자습서: ASP.NET Core를 사용하여 최소 API 만들기
참고 항목
이 문서의 최신 버전은 아닙니다. 현재 릴리스는 이 문서의 .NET 9 버전을 참조 하세요.
Important
이 정보는 상업적으로 출시되기 전에 실질적으로 수정될 수 있는 시험판 제품과 관련이 있습니다. Microsoft는 여기에 제공된 정보에 대해 어떠한 명시적, 또는 묵시적인 보증을 하지 않습니다.
현재 릴리스는 이 문서의 .NET 9 버전을 참조 하세요.
작성자: Rick Anderson 및 Tom Dykstra
최소 API는 최소한의 종속성으로 HTTP API를 만들도록 설계되었습니다. ASP.NET Core에 최소 파일, 기능 및 종속성만 포함하려는 마이크로 서비스 및 앱에 이상적입니다.
이 자습서에서는 ASP.NET Core를 사용한 최소 API를 빌드하는 기본 사항을 설명합니다. ASP.NET Core에서 API를 만드는 또 다른 방법은 컨트롤러를 사용하는 것입니다. 최소 API와 컨트롤러 기반 API 중에서 선택하는 방법에 대한 도움말은 API 개요를 참조하세요. 더 많은 기능이 포함된 컨트롤러를 기반으로 API 프로젝트를 만드는 방법에 대한 자습서는 웹 API 만들기를 참조하세요.
개요
이 자습서에서는 다음 API를 만듭니다.
API | 설명 | 요청 본문 | 응답 본문 |
---|---|---|---|
GET /todoitems |
할 일 항목 모두 가져오기 | None | 할 일 항목의 배열 |
GET /todoitems/complete |
완성된 할 일 항목 가져오기 | None | 할 일 항목의 배열 |
GET /todoitems/{id} |
ID로 항목 가져오기 | None | 할 일 항목 |
POST /todoitems |
새 항목 추가 | 할 일 항목 | 할 일 항목 |
PUT /todoitems/{id} |
기존 항목 업데이트 | 할 일 항목 | None |
DELETE /todoitems/{id} |
항목 삭제 | None | None |
필수 조건
API 프로젝트 만들기
Visual Studio 2022를 시작하고 새 프로젝트 만들기를 선택합니다.
새 프로젝트 만들기 대화 상자에서 다음을 수행합니다.
- 템플릿 검색 검색 상자에
Empty
를 입력합니다. - ASP.NET Core Empty 템플릿을 선택하고 다음을 선택합니다.
- 템플릿 검색 검색 상자에
프로젝트 이름을 TodoApi로 지정하고 다음을 선택합니다.
추가 정보 대화 상자에서 다음을 수행합니다.
- .NET 9.0(미리 보기) 선택
- 최상위 문 사용 안 함 선택 취소
- 만들기를 선택합니다.
코드 검사
Program.cs
파일에는 다음 코드가 포함되어 있습니다.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
앞의 코드가 하는 역할은 다음과 같습니다.
- 미리 구성된 기본값을 사용하여 WebApplicationBuilder 및 WebApplication을 만듭니다.
Hello World!
를 반환하는 HTTP GET 엔드포인트/
를 만듭니다.
앱 실행
Ctrl+F5를 눌러 디버거 없이 실행합니다.
Visual Studio는 다음 대화 상자를 표시합니다.
IIS Express SSL 인증서를 신뢰하는 경우 예를 선택합니다.
다음 대화 상자가 표시됩니다.
개발 인증서를 신뢰하는 데 동의하는 경우 예를 선택합니다.
Firefox 브라우저를 신뢰하는 방법에 대한 자세한 내용은 Firefox SEC_ERROR_INADEQUATE_KEY_USAGE 인증서 오류를 참조하세요.
Visual Studio에서 Kestrel 웹 서버를 시작하고 브라우저 창을 엽니다.
Hello World!
가 브라우저에 표시됩니다. Program.cs
파일에는 최소이지만 완전한 앱이 포함되어 있습니다.
브라우저 창을 닫습니다.
NuGet 패키지 추가
이 자습서에서 사용되는 데이터베이스 및 진단을 지원하려면 NuGet 패키지를 추가해야 합니다.
- 도구 메뉴에서 NuGet 패키지 관리자 > 솔루션용 NuGet 패키지 관리를 선택합니다.
- 찾아보기 탭을 선택합니다.
- 시험판 포함을 선택합니다.
- 검색 상자에 Microsoft.EntityFrameworkCore.InMemory를 입력한 다음
Microsoft.EntityFrameworkCore.InMemory
를 선택합니다. - 오른쪽 창에서 프로젝트 확인란을 선택하고 설치를 선택합니다.
- 이전 지침에 따라
Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
패키지를 추가합니다.
모델 및 데이터베이스 컨텍스트 클래스
- 프로젝트 폴더에서 다음 코드가 포함된
Todo.cs
라는 파일을 만듭니다.
public class Todo
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
}
앞의 코드는 이 앱에 대한 모델을 만듭니다. 모델은 앱에서 관리하는 데이터를 나타내는 일련의 클래스입니다.
- 다음 코드를 사용하여
TodoDb.cs
라는 파일을 만듭니다.
using Microsoft.EntityFrameworkCore;
class TodoDb : DbContext
{
public TodoDb(DbContextOptions<TodoDb> options)
: base(options) { }
public DbSet<Todo> Todos => Set<Todo>();
}
앞의 코드는 데이터 모델에 대한 Entity Framework 기능을 조정하는 기본 클래스인 데이터베이스 컨텍스트를 정의합니다. 이 클래스는 Microsoft.EntityFrameworkCore.DbContext 클래스에서 파생됩니다.
API 코드 추가
Program.cs
파일의 내용을 다음 코드로 바꿉니다.
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();
강조 표시된 다음 코드는 DI(종속성 주입) 컨테이너에 데이터베이스 컨텍스트를 추가하고 데이터베이스 관련 예외를 표시할 수 있도록 합니다.
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();
DI 컨테이너는 데이터베이스 컨텍스트 및 기타 서비스에 대한 액세스를 제공합니다.
이 자습서에서는 엔드포인트 탐색기 및 .http 파일을 사용하여 API를 테스트합니다.
데이터 게시 테스트
Program.cs
에서 다음 코드는 HTTP POST 엔드포인트 /todoitems
를 만들어 메모리 내 데이터베이스에 데이터를 추가합니다.
app.MapPost("/todoitems", async (Todo todo, TodoDb db) =>
{
db.Todos.Add(todo);
await db.SaveChangesAsync();
return Results.Created($"/todoitems/{todo.Id}", todo);
});
앱을 실행합니다. 더 이상 /
엔드포인트가 없으므로 브라우저에 404 오류가 표시됩니다.
POST 엔드포인트는 앱에 데이터를 추가하는 데 사용됩니다.
다른 Windows>엔드포인트 탐색기 보기를>선택합니다.
POST 엔드포인트를 마우스 오른쪽 단추로 클릭하고 요청 생성을 선택합니다.
다음 예제와 유사한 내용이 포함된 새 파일이 프로젝트 폴더
TodoApi.http
에 만들어집니다.@TodoApi_HostAddress = https://localhost:7031 Post {{TodoApi_HostAddress}}/todoitems ###
- 첫 번째 줄은 모든 엔드포인트에 사용되는 변수를 만듭니다.
- 다음 줄은 POST 요청을 정의합니다.
- 삼중 해시 태그(
###
) 줄은 요청 구분 기호입니다. 이는 다른 요청에 대한 것입니다.
POST 요청에는 헤더와 본문이 필요합니다. 요청의 해당 부분을 정의하려면 POST 요청 줄 바로 다음에 다음 줄을 추가합니다.
Content-Type: application/json { "name":"walk dog", "isComplete":true }
앞의 코드는 Content-Type 헤더와 JSON 요청 본문을 추가합니다. 이제 TodoApi.http 파일은 다음 예제와 비슷하지만 포트 번호가 있습니다.
@TodoApi_HostAddress = https://localhost:7057 Post {{TodoApi_HostAddress}}/todoitems Content-Type: application/json { "name":"walk dog", "isComplete":true } ###
앱을 실행합니다.
요청 줄 위에
POST
있는 요청 보내기 링크를 선택합니다.POST 요청이 앱으로 전송되고 응답 창에 응답이 표시됩니다.
GET 엔드포인트 검사
샘플 앱은 MapGet
을 호출하여 여러 GET 엔드포인트를 구현합니다.
API | 설명 | 요청 본문 | 응답 본문 |
---|---|---|---|
GET /todoitems |
할 일 항목 모두 가져오기 | None | 할 일 항목의 배열 |
GET /todoitems/complete |
완성된 모든 할 일 항목 가져오기 | None | 할 일 항목의 배열 |
GET /todoitems/{id} |
ID로 항목 가져오기 | None | 할 일 항목 |
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());
GET 엔드포인트 테스트
브라우저에서 엔드포인트를 GET
호출하거나 엔드포인트 탐색기를 사용하여 앱을 테스트합니다. 다음 단계는 엔드포인트 탐색기에 대한 것입니다.
엔드포인트 탐색기에서 첫 번째 GET 엔드포인트를 마우스 오른쪽 단추로 클릭하고 요청 생성을 선택합니다.
다음 콘텐츠가 파일에 추가
TodoApi.http
됩니다.Get {{TodoApi_HostAddress}}/todoitems ###
새
GET
요청 줄 위에 있는 요청 보내기 링크를 선택합니다.GET 요청이 앱으로 전송되고 응답 창에 응답이 표시됩니다.
응답 본문은 다음 JSON과 유사합니다.
[ { "id": 1, "name": "walk dog", "isComplete": true } ]
엔드포인트 탐색기에서 GET 엔드포인트를 마우스 오른쪽 단추로
/todoitems/{id}
클릭하고 요청 생성을 선택합니다. 다음 콘텐츠가 파일에 추가TodoApi.http
됩니다.GET {{TodoApi_HostAddress}}/todoitems/{id} ###
{id}
를1
로 교체합니다.새 GET 요청 줄 위에 있는 요청 보내기 링크를 선택합니다.
GET 요청이 앱으로 전송되고 응답 창에 응답이 표시됩니다.
응답 본문은 다음 JSON과 유사합니다.
{ "id": 1, "name": "walk dog", "isComplete": true }
이 앱은 메모리 내 데이터베이스를 사용합니다. 앱이 다시 시작되면 GET 요청이 데이터를 반환하지 않습니다. 데이터가 반환되지 않으면 POST 데이터를 앱에 게시하고 GET 요청을 다시 시도합니다.
반환 값
ASP.NET Core는 자동으로 JSON에 개체를 직렬화하고 JSON을 응답 메시지의 본문에 기록합니다. 이 반환 형식의 응답 코드는 200 OK이며 처리되지 않은 예외가 없다고 가정합니다. 처리되지 않은 예외는 5xx 오류로 변환됩니다.
반환 형식은 다양한 HTTP 상태 코드를 나타낼 수 있습니다. 예를 들어 GET /todoitems/{id}
은 두 가지 상태 값을 반환할 수 있습니다.
- 요청된 ID와 일치하는 항목이 없는 경우 메서드에서 404 상태 NotFound 오류 코드를 반환합니다.
- 그렇지 않으면 메서드가 JSON 응답 본문에서 200을 반환합니다.
item
을 반환하면 HTTP 200 응답이 발생합니다.
PUT 엔드포인트 검사
샘플 앱은 MapPut
을 사용하여 단일 PUT 엔드포인트를 구현합니다.
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();
});
이 메서드는 HTTP PUT을 사용한다는 점을 제외하고 MapPost
메서드와 비슷합니다. 성공적인 응답은 204(콘텐츠 없음)를 반환합니다. HTTP 사양에 따라 PUT 요청의 경우 클라이언트는 변경 내용만이 아니라 전체 업데이트된 엔터티를 보내야 합니다. 부분 업데이트를 지원하려면 HTTP PATCH를 사용합니다.
PUT 엔드포인트 테스트
이 샘플은 앱이 시작될 때마다 초기화되어야 하는 메모리 내 데이터베이스를 사용합니다. PUT 호출을 실행하기 전에 데이터베이스에 항목이 있어야 합니다. GET을 호출하여 PUT 호출을 실행하기 전에 데이터베이스에 항목이 있는지 확인합니다.
할 일 항목을 Id = 1
업데이트하고 이름을 "feed fish"
.로 설정합니다.
엔드포인트 탐색기에서 PUT 엔드포인트를 마우스 오른쪽 단추로 클릭하고 요청 생성을 선택합니다.
다음 콘텐츠가 파일에 추가
TodoApi.http
됩니다.Put {{TodoApi_HostAddress}}/todoitems/{id} ###
PUT 요청 줄
{id}
1
에서 .PUT 요청 줄 바로 다음에 다음 줄을 추가합니다.
Content-Type: application/json { "name": "feed fish", "isComplete": false }
앞의 코드는 Content-Type 헤더와 JSON 요청 본문을 추가합니다.
새 PUT 요청 줄 위에 있는 요청 보내기 링크를 선택합니다.
PUT 요청이 앱으로 전송되고 응답 창에 응답이 표시됩니다. 응답 본문이 비어 있고 상태 코드는 204입니다.
DELETE 엔드포인트 검사 및 테스트
샘플 앱은 MapDelete
를 사용하여 단일 DELETE 엔드포인트를 구현합니다.
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();
});
엔드포인트 탐색기에서 DELETE 엔드포인트를 마우스 오른쪽 단추로 클릭하고 요청 생성을 선택합니다.
DELETE 요청이 에 추가
TodoApi.http
됩니다.DELETE 요청 줄에서 .로 바
1
꿉{id}
다. DELETE 요청은 다음 예제와 같습니다.DELETE {{TodoApi_HostAddress}}/todoitems/1 ###
DELETE 요청에 대한 요청 보내기 링크를 선택합니다.
DELETE 요청이 앱으로 전송되고 응답 창에 응답이 표시됩니다. 응답 본문이 비어 있고 상태 코드는 204입니다.
MapGroup API 사용
샘플 앱 코드는 엔드포인트를 설정할 때마다 todoitems
URL 접두사를 반복합니다. API에는 공통 URL 접두사를 사용하는 엔드포인트 그룹이 있는 경우가 많으며 MapGroup 메서드를 사용하여 이러한 그룹을 구성할 수 있습니다. 반복 코드를 줄이고 RequireAuthorization 및 WithMetadata와 같은 메서드에 대한 단일 호출로 전체 엔드포인트 그룹을 사용자 지정할 수 있습니다.
Program.cs
의 내용을 다음 코드로 바꿉니다.
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();
위의 코드에는 다음과 같은 변경 내용이 있습니다.
- URL 접두사
/todoitems
을 사용하여 그룹을 설정하도록var todoItems = app.MapGroup("/todoitems");
을 추가합니다. - 모든
app.Map<HttpVerb>
메서드를todoItems.Map<HttpVerb>
로 변경합니다. Map<HttpVerb>
메서드 호출에서 URL 접두사/todoitems
를 제거합니다.
엔드포인트를 테스트하여 동일하게 작동하는지 확인합니다.
TypedResults API 사용
TypedResults 테스트 용이성 및 엔드포인트를 설명하기 위해 OpenAPI에 대한 응답 형식 메타데이터를 자동으로 반환하는 등 여러 가지 이점이 있는 대신 Results 반환합니다. 자세한 내용은 TypedResults 및 결과를 참조하세요.
Map<HttpVerb>
메서드는 람다를 사용하는 대신 경로 처리기 메서드를 호출할 수 있습니다. 예제를 보려면 다음 코드로 Program.cs를 업데이트합니다.
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();
}
이제 Map<HttpVerb>
코드는 람다 대신 메서드를 호출합니다.
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);
이러한 메서드는 IResult를 구현하고 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();
}
단위 테스트는 이러한 메서드를 호출하고 올바른 형식을 반환하는지 테스트할 수 있습니다. 예를 들어 메서드가 GetAllTodos
인 경우:
static async Task<IResult> GetAllTodos(TodoDb db)
{
return TypedResults.Ok(await db.Todos.ToArrayAsync());
}
단위 테스트 코드는 Ok<Todo[]> 형식의 개체가 처리기 메서드에서 반환되는지 확인할 수 있습니다. 예시:
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);
}
과도한 게시 방지
현재 샘플 앱은 전체 Todo
개체를 공개합니다. 프로덕션 앱 프로덕션 애플리케이션에서 모델의 하위 집합은 입력 및 반환될 수 있는 데이터를 제한하는 데 자주 사용됩니다. 이 동작에는 여러 가지 이유가 있으며, 보안이 주요 이유 중 하나입니다. 일반적으로 모델의 하위 집합을 DTO(데이터 전송 개체), 입력 모델 또는 뷰 모델이라고 합니다. 이 문서에서는 DTO를 사용합니다.
DTO를 사용하여 다음을 수행할 수 있습니다.
- 과도한 게시를 방지합니다.
- 클라이언트에서 볼 수 없는 속성을 숨깁니다.
- 페이로드 크기를 줄이기 위해 일부 속성을 생략합니다.
- 중첩된 개체를 포함하는 개체 그래프를 평면화합니다. 클라이언트에는 평면화된 개체 그래프가 더 편리할 수 있습니다.
DTO 방법을 설명하려면 비밀 필드를 포함하도록 Todo
클래스를 업데이트합니다.
public class Todo
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
public string? Secret { get; set; }
}
이 앱에서는 숨겨진 필드를 숨겨야 하지만, 관리 앱은 숨겨진 필드를 공개할 수 있습니다.
비밀 필드를 게시하고 가져올 수 있는지 확인합니다.
다음 코드를 사용하여 TodoItemDTO.cs
라는 파일을 만듭니다.
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);
}
이 DTO 모델을 사용하려면 파일의 Program.cs
내용을 다음 코드로 바꿉니다.
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();
}
비밀 필드를 제외한 모든 필드를 게시하고 가져올 수 있는지 확인합니다.
완료된 샘플 문제 해결
해결할 수 없는 문제가 발생한 경우 완료된 프로젝트와 코드를 비교합니다. 완료된 프로젝트를 보거나 다운로드합니다(다운로드 방법).
다음 단계
- JSON serialization 옵션을 구성합니다.
- 오류 및 예외 처리: 개발자 예외 페이지는 최소 API 앱에 대해 개발 환경에서 기본적으로 사용하도록 설정됩니다. 오류 및 예외를 처리하는 방법에 대한 자세한 내용은 ASP.NET Core API에서 오류 처리를 참조하세요.
- 최소 API 앱 테스트의 예는 이 GitHub 샘플을 참조하세요.
- 최소 API에서 OpenAPI를 지원합니다.
- 빠른 시작: Azure에 게시합니다.
- ASP.NET Core 최소 API 구성
자세한 정보
최소 API는 최소한의 종속성으로 HTTP API를 만들도록 설계되었습니다. ASP.NET Core에 최소 파일, 기능, 종속성만 포함하려는 앱과 마이크로 서비스에 적합합니다.
이 자습서에서는 ASP.NET Core를 사용한 최소 API를 빌드하는 기본 사항을 설명합니다. ASP.NET Core에서 API를 만드는 또 다른 방법은 컨트롤러를 사용하는 것입니다. 최소 API와 컨트롤러 기반 API 중에서 선택하는 방법에 대한 도움말은 API 개요를 참조하세요. 더 많은 기능이 포함된 컨트롤러를 기반으로 API 프로젝트를 만드는 방법에 대한 자습서는 웹 API 만들기를 참조하세요.
개요
이 자습서에서는 다음 API를 만듭니다.
API | 설명 | 요청 본문 | 응답 본문 |
---|---|---|---|
GET /todoitems |
할 일 항목 모두 가져오기 | None | 할 일 항목의 배열 |
GET /todoitems/complete |
완성된 할 일 항목 가져오기 | None | 할 일 항목의 배열 |
GET /todoitems/{id} |
ID로 항목 가져오기 | None | 할 일 항목 |
POST /todoitems |
새 항목 추가 | 할 일 항목 | 할 일 항목 |
PUT /todoitems/{id} |
기존 항목 업데이트 | 할 일 항목 | None |
DELETE /todoitems/{id} |
항목 삭제 | None | None |
필수 조건
ASP.NET 및 웹 개발 워크로드가 있는 Visual Studio 2022
API 프로젝트 만들기
Visual Studio 2022를 시작하고 새 프로젝트 만들기를 선택합니다.
새 프로젝트 만들기 대화 상자에서 다음을 수행합니다.
- 템플릿 검색 검색 상자에
Empty
를 입력합니다. - ASP.NET Core Empty 템플릿을 선택하고 다음을 선택합니다.
- 템플릿 검색 검색 상자에
프로젝트 이름을 TodoApi로 지정하고 다음을 선택합니다.
추가 정보 대화 상자에서 다음을 수행합니다.
- .NET 7.0 선택
- 최상위 문 사용 안 함 선택 취소
- 만들기를 선택합니다.
코드 검사
Program.cs
파일에는 다음 코드가 포함되어 있습니다.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
앞의 코드가 하는 역할은 다음과 같습니다.
- 미리 구성된 기본값을 사용하여 WebApplicationBuilder 및 WebApplication을 만듭니다.
Hello World!
를 반환하는 HTTP GET 엔드포인트/
를 만듭니다.
앱 실행
Ctrl+F5를 눌러 디버거 없이 실행합니다.
Visual Studio는 다음 대화 상자를 표시합니다.
IIS Express SSL 인증서를 신뢰하는 경우 예를 선택합니다.
다음 대화 상자가 표시됩니다.
개발 인증서를 신뢰하는 데 동의하는 경우 예를 선택합니다.
Firefox 브라우저를 신뢰하는 방법에 대한 자세한 내용은 Firefox SEC_ERROR_INADEQUATE_KEY_USAGE 인증서 오류를 참조하세요.
Visual Studio에서 Kestrel 웹 서버를 시작하고 브라우저 창을 엽니다.
Hello World!
가 브라우저에 표시됩니다. Program.cs
파일에는 최소이지만 완전한 앱이 포함되어 있습니다.
NuGet 패키지 추가
이 자습서에서 사용되는 데이터베이스 및 진단을 지원하려면 NuGet 패키지를 추가해야 합니다.
- 도구 메뉴에서 NuGet 패키지 관리자 > 솔루션용 NuGet 패키지 관리를 선택합니다.
- 찾아보기 탭을 선택합니다.
- 검색 상자에 Microsoft.EntityFrameworkCore.InMemory를 입력한 다음
Microsoft.EntityFrameworkCore.InMemory
를 선택합니다. - 오른쪽 창에서 프로젝트 확인란을 선택합니다.
- 버전 드롭다운에서 사용 가능한 최신 버전 7을
7.0.17
선택한 다음 설치를 선택합니다. - 위의 지침에 따라 사용 가능한 최신 버전 7로 패키지를 추가
Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
합니다.
모델 및 데이터베이스 컨텍스트 클래스
프로젝트 폴더에서 다음 코드가 포함된 Todo.cs
라는 파일을 만듭니다.
public class Todo
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
}
앞의 코드는 이 앱에 대한 모델을 만듭니다. 모델은 앱에서 관리하는 데이터를 나타내는 일련의 클래스입니다.
다음 코드를 사용하여 TodoDb.cs
라는 파일을 만듭니다.
using Microsoft.EntityFrameworkCore;
class TodoDb : DbContext
{
public TodoDb(DbContextOptions<TodoDb> options)
: base(options) { }
public DbSet<Todo> Todos => Set<Todo>();
}
앞의 코드는 데이터 모델에 대한 Entity Framework 기능을 조정하는 기본 클래스인 데이터베이스 컨텍스트를 정의합니다. 이 클래스는 Microsoft.EntityFrameworkCore.DbContext 클래스에서 파생됩니다.
API 코드 추가
Program.cs
파일의 내용을 다음 코드로 바꿉니다.
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();
강조 표시된 다음 코드는 DI(종속성 주입) 컨테이너에 데이터베이스 컨텍스트를 추가하고 데이터베이스 관련 예외를 표시할 수 있도록 합니다.
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();
DI 컨테이너는 데이터베이스 컨텍스트 및 기타 서비스에 대한 액세스를 제공합니다.
Swagger를 사용하여 API 테스트 UI 만들기
선택할 수 있는 여러 웹 API 테스트 도구가 있으며, 원하는 도구를 사용하여 이 자습서의 소개 API 테스트 단계를 따를 수 있습니다.
이 자습서에서는 OpenAPI 사양을 준수하는 테스트 UI를 생성하기 위한 Swagger 도구를 통합하는 .NET 패키지 NSwag.AspNetCore를 활용합니다.
- NSwag: Swagger를 ASP.NET Core 애플리케이션에 직접 통합하여 미들웨어 및 구성을 제공하는 .NET 라이브러리입니다.
- Swagger: OpenAPI 사양을 따르는 API 테스트 페이지를 생성하는 OpenAPIGenerator 및 SwaggerUI와 같은 오픈 소스 도구 집합입니다.
- OpenAPI 사양: 컨트롤러 및 모델 내의 XML 및 특성 주석을 기반으로 API의 기능을 설명하는 문서입니다.
ASP.NET OpenAPI 및 NSwag를 사용하는 방법에 대한 자세한 내용은 Swagger/OpenAPI를 사용하는 ASP.NET Core Web API 설명서를 참조하세요.
Swagger 도구 설치
다음 명령을 실행합니다.
dotnet add package NSwag.AspNetCore
이전 명령은 Swagger 문서 및 UI를 생성하는 도구가 포함된 NSwag.AspNetCore 패키지를 추가합니다.
Swagger 미들웨어 구성
줄에 정의되기 전에
app
강조 표시된 다음 코드를 추가합니다.var 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();
위의 코드에서:
builder.Services.AddEndpointsApiExplorer();
: HTTP API에 대한 메타데이터를 제공하는 서비스인 API 탐색기를 사용하도록 설정합니다. API 탐색기는 Swagger에서 Swagger 문서를 생성하는 데 사용됩니다.builder.Services.AddOpenApiDocument(config => {...});
: Swagger OpenAPI 문서 생성기를 애플리케이션 서비스에 추가하고 해당 제목 및 버전과 같은 API에 대한 자세한 정보를 제공하도록 구성합니다. 보다 강력한 API 세부 정보를 제공하는 방법에 대한 자세한 내용은 NSwag 및 ASP.NET Core 시작을 참조하세요.다음 강조 표시된 코드를 줄에 정의한 후
app
다음 줄에 추가합니다.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"; }); }
이전 코드를 사용하면 Swagger 미들웨어가 생성된 JSON 문서 및 Swagger UI를 제공할 수 있습니다. Swagger는 개발 환경에서만 사용할 수 있습니다. 프로덕션 환경에서 Swagger를 사용하도록 설정하면 API의 구조 및 구현에 대한 잠재적으로 중요한 세부 정보가 노출될 수 있습니다.
데이터 게시 테스트
Program.cs
에서 다음 코드는 HTTP POST 엔드포인트 /todoitems
를 만들어 메모리 내 데이터베이스에 데이터를 추가합니다.
app.MapPost("/todoitems", async (Todo todo, TodoDb db) =>
{
db.Todos.Add(todo);
await db.SaveChangesAsync();
return Results.Created($"/todoitems/{todo.Id}", todo);
});
앱을 실행합니다. 더 이상 /
엔드포인트가 없으므로 브라우저에 404 오류가 표시됩니다.
POST 엔드포인트는 앱에 데이터를 추가하는 데 사용됩니다.
앱이 계속 실행 중인 상태에서 브라우저에서 Swagger에서 생성된 API 테스트 페이지를 표시하도록
https://localhost:<port>/swagger
이동합니다.Swagger API 테스트 페이지에서 Post /todoitems>Try it out을 선택합니다.
요청 본문 필드에는 API에 대한 매개 변수를 반영하는 생성된 예제 형식이 포함되어 있습니다.
요청 본문에서 선택 사항을
id
지정하지 않고 할 일 항목에 대한 JSON을 입력합니다.{ "name":"walk dog", "isComplete":true }
실행을 선택합니다.
Swagger는 실행 단추 아래에 응답 창을 제공합니다.
몇 가지 유용한 세부 정보를 확인합니다.
- cURL: Swagger는 Unix/Linux 구문의 예제 cURL 명령을 제공합니다. 이 명령은 Windows용 Git의 Git Bash를 포함하여 Unix/Linux 구문을 사용하는 bash 셸을 사용하여 명령줄에서 실행할 수 있습니다.
- 요청 URL: API 호출에 대한 Swagger UI의 JavaScript 코드에서 수행한 HTTP 요청의 간소화된 표현입니다. 실제 요청에는 헤더, 쿼리 매개 변수 및 요청 본문과 같은 세부 정보가 포함될 수 있습니다.
- 서버 응답: 응답 본문 및 헤더를 포함합니다. 응답 본문은 설정된 것을
id
보여 줍니다1
. - 응답 코드: 요청이 성공적으로 처리되고 새 리소스가 생성되었음을 나타내는 201
HTTP
상태 코드가 반환되었습니다.
GET 엔드포인트 검사
샘플 앱은 MapGet
을 호출하여 여러 GET 엔드포인트를 구현합니다.
API | 설명 | 요청 본문 | 응답 본문 |
---|---|---|---|
GET /todoitems |
할 일 항목 모두 가져오기 | None | 할 일 항목의 배열 |
GET /todoitems/complete |
완성된 모든 할 일 항목 가져오기 | None | 할 일 항목의 배열 |
GET /todoitems/{id} |
ID로 항목 가져오기 | None | 할 일 항목 |
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());
GET 엔드포인트 테스트
브라우저 또는 Swagger에서 엔드포인트를 호출하여 앱을 테스트합니다.
Swagger에서 GET /todoitems를 선택하여 실행해 보세요>.>
또는 URI
http://localhost:<port>/todoitems
를 입력하여 브라우저에서 GET /todoitems를 호출합니다. 예를 들어http://localhost:5001/todoitems
GET /todoitems
를 호출하면 다음과 비슷한 응답이 생성됩니다.
[
{
"id": 1,
"name": "walk dog",
"isComplete": true
}
]
Swagger에서 GET /todoitems/{id}를 호출하여 특정 ID에서 데이터를 반환합니다.
- GET /todoitems Try it out을>선택합니다.
- ID 필드를
1
설정하고 실행을 선택합니다.
또는 URI
https://localhost:<port>/todoitems/1
를 입력하여 브라우저에서 GET /todoitems를 호출합니다. 예를 들어https://localhost:5001/todoitems/1
응답은 다음과 비슷합니다.
{ "id": 1, "name": "walk dog", "isComplete": true }
이 앱은 메모리 내 데이터베이스를 사용합니다. 앱이 다시 시작되면 GET 요청이 데이터를 반환하지 않습니다. 데이터가 반환되지 않으면 POST 데이터를 앱에 게시하고 GET 요청을 다시 시도합니다.
반환 값
ASP.NET Core는 자동으로 JSON에 개체를 직렬화하고 JSON을 응답 메시지의 본문에 기록합니다. 이 반환 형식의 응답 코드는 200 OK이며 처리되지 않은 예외가 없다고 가정합니다. 처리되지 않은 예외는 5xx 오류로 변환됩니다.
반환 형식은 다양한 HTTP 상태 코드를 나타낼 수 있습니다. 예를 들어 GET /todoitems/{id}
은 두 가지 상태 값을 반환할 수 있습니다.
- 요청된 ID와 일치하는 항목이 없는 경우 메서드에서 404 상태 NotFound 오류 코드를 반환합니다.
- 그렇지 않으면 메서드가 JSON 응답 본문에서 200을 반환합니다.
item
을 반환하면 HTTP 200 응답이 발생합니다.
PUT 엔드포인트 검사
샘플 앱은 MapPut
을 사용하여 단일 PUT 엔드포인트를 구현합니다.
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();
});
이 메서드는 HTTP PUT을 사용한다는 점을 제외하고 MapPost
메서드와 비슷합니다. 성공적인 응답은 204(콘텐츠 없음)를 반환합니다. HTTP 사양에 따라 PUT 요청의 경우 클라이언트는 변경 내용만이 아니라 전체 업데이트된 엔터티를 보내야 합니다. 부분 업데이트를 지원하려면 HTTP PATCH를 사용합니다.
PUT 엔드포인트 테스트
이 샘플은 앱이 시작될 때마다 초기화되어야 하는 메모리 내 데이터베이스를 사용합니다. PUT 호출을 실행하기 전에 데이터베이스에 항목이 있어야 합니다. GET을 호출하여 PUT 호출을 실행하기 전에 데이터베이스에 항목이 있는지 확인합니다.
할 일 항목을 Id = 1
업데이트하고 이름을 "feed fish"
.로 설정합니다.
Swagger를 사용하여 PUT 요청을 보냅니다.
/todoitems/{id}>놓기를 선택합니다.
ID 필드를 .로
1
설정합니다.요청 본문을 다음 JSON으로 설정합니다.
{ "name": "feed fish", "isComplete": false }
실행을 선택합니다.
DELETE 엔드포인트 검사 및 테스트
샘플 앱은 MapDelete
를 사용하여 단일 DELETE 엔드포인트를 구현합니다.
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();
});
Swagger를 사용하여 DELETE 요청을 보냅니다.
DELETE /todoitems/{id}>사용해 보세요.
ID 필드를
1
설정하고 실행을 선택합니다.DELETE 요청이 앱으로 전송되고 응답 창에 응답이 표시됩니다. 응답 본문이 비어 있고 서버 응답 상태 코드는 204입니다.
MapGroup API 사용
샘플 앱 코드는 엔드포인트를 설정할 때마다 todoitems
URL 접두사를 반복합니다. API에는 공통 URL 접두사를 사용하는 엔드포인트 그룹이 있는 경우가 많으며 MapGroup 메서드를 사용하여 이러한 그룹을 구성할 수 있습니다. 반복 코드를 줄이고 RequireAuthorization 및 WithMetadata와 같은 메서드에 대한 단일 호출로 전체 엔드포인트 그룹을 사용자 지정할 수 있습니다.
Program.cs
의 내용을 다음 코드로 바꿉니다.
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();
위의 코드에는 다음과 같은 변경 내용이 있습니다.
- URL 접두사
/todoitems
을 사용하여 그룹을 설정하도록var todoItems = app.MapGroup("/todoitems");
을 추가합니다. - 모든
app.Map<HttpVerb>
메서드를todoItems.Map<HttpVerb>
로 변경합니다. Map<HttpVerb>
메서드 호출에서 URL 접두사/todoitems
를 제거합니다.
엔드포인트를 테스트하여 동일하게 작동하는지 확인합니다.
TypedResults API 사용
TypedResults 테스트 용이성 및 엔드포인트를 설명하기 위해 OpenAPI에 대한 응답 형식 메타데이터를 자동으로 반환하는 등 여러 가지 이점이 있는 대신 Results 반환합니다. 자세한 내용은 TypedResults 및 결과를 참조하세요.
Map<HttpVerb>
메서드는 람다를 사용하는 대신 경로 처리기 메서드를 호출할 수 있습니다. 예제를 보려면 다음 코드로 Program.cs를 업데이트합니다.
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();
}
이제 Map<HttpVerb>
코드는 람다 대신 메서드를 호출합니다.
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);
이러한 메서드는 IResult를 구현하고 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();
}
단위 테스트는 이러한 메서드를 호출하고 올바른 형식을 반환하는지 테스트할 수 있습니다. 예를 들어 메서드가 GetAllTodos
인 경우:
static async Task<IResult> GetAllTodos(TodoDb db)
{
return TypedResults.Ok(await db.Todos.ToArrayAsync());
}
단위 테스트 코드는 Ok<Todo[]> 형식의 개체가 처리기 메서드에서 반환되는지 확인할 수 있습니다. 예시:
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);
}
과도한 게시 방지
현재 샘플 앱은 전체 Todo
개체를 공개합니다. 프로덕션 앱 프로덕션 애플리케이션에서 모델의 하위 집합은 입력 및 반환될 수 있는 데이터를 제한하는 데 자주 사용됩니다. 이 동작에는 여러 가지 이유가 있으며, 보안이 주요 이유 중 하나입니다. 일반적으로 모델의 하위 집합을 DTO(데이터 전송 개체), 입력 모델 또는 뷰 모델이라고 합니다. 이 문서에서는 DTO를 사용합니다.
DTO를 사용하여 다음을 수행할 수 있습니다.
- 과도한 게시를 방지합니다.
- 클라이언트에서 볼 수 없는 속성을 숨깁니다.
- 페이로드 크기를 줄이기 위해 일부 속성을 생략합니다.
- 중첩된 개체를 포함하는 개체 그래프를 평면화합니다. 클라이언트에는 평면화된 개체 그래프가 더 편리할 수 있습니다.
DTO 방법을 설명하려면 비밀 필드를 포함하도록 Todo
클래스를 업데이트합니다.
public class Todo
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
public string? Secret { get; set; }
}
이 앱에서는 숨겨진 필드를 숨겨야 하지만, 관리 앱은 숨겨진 필드를 공개할 수 있습니다.
비밀 필드를 게시하고 가져올 수 있는지 확인합니다.
다음 코드를 사용하여 TodoItemDTO.cs
라는 파일을 만듭니다.
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);
}
이 DTO 모델을 사용하려면 파일의 Program.cs
내용을 다음 코드로 바꿉니다.
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>();
}
비밀 필드를 제외한 모든 필드를 게시하고 가져올 수 있는지 확인합니다.
완료된 샘플 문제 해결
해결할 수 없는 문제가 발생한 경우 완료된 프로젝트와 코드를 비교합니다. 완료된 프로젝트를 보거나 다운로드합니다(다운로드 방법).
다음 단계
- JSON serialization 옵션을 구성합니다.
- 오류 및 예외 처리: 개발자 예외 페이지는 최소 API 앱에 대해 개발 환경에서 기본적으로 사용하도록 설정됩니다. 오류 및 예외를 처리하는 방법에 대한 자세한 내용은 ASP.NET Core API에서 오류 처리를 참조하세요.
- 최소 API 앱 테스트의 예는 이 GitHub 샘플을 참조하세요.
- 최소 API에서 OpenAPI를 지원합니다.
- 빠른 시작: Azure에 게시합니다.
- ASP.NET Core 최소 API 구성
자세한 정보
최소 API는 최소한의 종속성으로 HTTP API를 만들도록 설계되었습니다. ASP.NET Core에 최소 파일, 기능, 종속성만 포함하려는 앱과 마이크로 서비스에 적합합니다.
이 자습서에서는 ASP.NET Core를 사용한 최소 API를 빌드하는 기본 사항을 설명합니다. ASP.NET Core에서 API를 만드는 또 다른 방법은 컨트롤러를 사용하는 것입니다. 최소 API와 컨트롤러 기반 API 중에서 선택하는 방법에 대한 도움말은 API 개요를 참조하세요. 더 많은 기능이 포함된 컨트롤러를 기반으로 API 프로젝트를 만드는 방법에 대한 자습서는 웹 API 만들기를 참조하세요.
개요
이 자습서에서는 다음 API를 만듭니다.
API | 설명 | 요청 본문 | 응답 본문 |
---|---|---|---|
GET /todoitems |
할 일 항목 모두 가져오기 | None | 할 일 항목의 배열 |
GET /todoitems/complete |
완성된 할 일 항목 가져오기 | None | 할 일 항목의 배열 |
GET /todoitems/{id} |
ID로 항목 가져오기 | None | 할 일 항목 |
POST /todoitems |
새 항목 추가 | 할 일 항목 | 할 일 항목 |
PUT /todoitems/{id} |
기존 항목 업데이트 | 할 일 항목 | None |
DELETE /todoitems/{id} |
항목 삭제 | None | None |
필수 조건
- ASP.NET 및 웹 개발 워크로드가 있는 Visual Studio 2022
- .NET 6.0 SDK
API 프로젝트 만들기
Visual Studio 2022를 시작하고 새 프로젝트 만들기를 선택합니다.
새 프로젝트 만들기 대화 상자에서 다음을 수행합니다.
- 템플릿 검색 검색 상자에
Empty
를 입력합니다. - ASP.NET Core Empty 템플릿을 선택하고 다음을 선택합니다.
- 템플릿 검색 검색 상자에
프로젝트 이름을 TodoApi로 지정하고 다음을 선택합니다.
추가 정보 대화 상자에서 다음을 수행합니다.
- .NET 6.0 선택
- 최상위 문 사용 안 함 선택 취소
- 만들기를 선택합니다.
코드 검사
Program.cs
파일에는 다음 코드가 포함되어 있습니다.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
앞의 코드가 하는 역할은 다음과 같습니다.
- 미리 구성된 기본값을 사용하여 WebApplicationBuilder 및 WebApplication을 만듭니다.
Hello World!
를 반환하는 HTTP GET 엔드포인트/
를 만듭니다.
앱 실행
Ctrl+F5를 눌러 디버거 없이 실행합니다.
Visual Studio는 다음 대화 상자를 표시합니다.
IIS Express SSL 인증서를 신뢰하는 경우 예를 선택합니다.
다음 대화 상자가 표시됩니다.
개발 인증서를 신뢰하는 데 동의하는 경우 예를 선택합니다.
Firefox 브라우저를 신뢰하는 방법에 대한 자세한 내용은 Firefox SEC_ERROR_INADEQUATE_KEY_USAGE 인증서 오류를 참조하세요.
Visual Studio에서 Kestrel 웹 서버를 시작하고 브라우저 창을 엽니다.
Hello World!
가 브라우저에 표시됩니다. Program.cs
파일에는 최소이지만 완전한 앱이 포함되어 있습니다.
NuGet 패키지 추가
이 자습서에서 사용되는 데이터베이스 및 진단을 지원하려면 NuGet 패키지를 추가해야 합니다.
- 도구 메뉴에서 NuGet 패키지 관리자 > 솔루션용 NuGet 패키지 관리를 선택합니다.
- 찾아보기 탭을 선택합니다.
- 검색 상자에 Microsoft.EntityFrameworkCore.InMemory를 입력한 다음
Microsoft.EntityFrameworkCore.InMemory
를 선택합니다. - 오른쪽 창에서 프로젝트 확인란을 선택합니다.
- 버전 드롭다운에서 사용 가능한 최신 버전 7을
6.0.28
선택한 다음 설치를 선택합니다. - 위의 지침에 따라 사용 가능한 최신 버전 7로 패키지를 추가
Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
합니다.
모델 및 데이터베이스 컨텍스트 클래스
프로젝트 폴더에서 다음 코드가 포함된 Todo.cs
라는 파일을 만듭니다.
public class Todo
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
}
앞의 코드는 이 앱에 대한 모델을 만듭니다. 모델은 앱에서 관리하는 데이터를 나타내는 일련의 클래스입니다.
다음 코드를 사용하여 TodoDb.cs
라는 파일을 만듭니다.
using Microsoft.EntityFrameworkCore;
class TodoDb : DbContext
{
public TodoDb(DbContextOptions<TodoDb> options)
: base(options) { }
public DbSet<Todo> Todos => Set<Todo>();
}
앞의 코드는 데이터 모델에 대한 Entity Framework 기능을 조정하는 기본 클래스인 데이터베이스 컨텍스트를 정의합니다. 이 클래스는 Microsoft.EntityFrameworkCore.DbContext 클래스에서 파생됩니다.
API 코드 추가
Program.cs
파일의 내용을 다음 코드로 바꿉니다.
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();
강조 표시된 다음 코드는 DI(종속성 주입) 컨테이너에 데이터베이스 컨텍스트를 추가하고 데이터베이스 관련 예외를 표시할 수 있도록 합니다.
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();
DI 컨테이너는 데이터베이스 컨텍스트 및 기타 서비스에 대한 액세스를 제공합니다.
Swagger를 사용하여 API 테스트 UI 만들기
선택할 수 있는 여러 웹 API 테스트 도구가 있으며, 원하는 도구를 사용하여 이 자습서의 소개 API 테스트 단계를 따를 수 있습니다.
이 자습서에서는 OpenAPI 사양을 준수하는 테스트 UI를 생성하기 위한 Swagger 도구를 통합하는 .NET 패키지 NSwag.AspNetCore를 활용합니다.
- NSwag: Swagger를 ASP.NET Core 애플리케이션에 직접 통합하여 미들웨어 및 구성을 제공하는 .NET 라이브러리입니다.
- Swagger: OpenAPI 사양을 따르는 API 테스트 페이지를 생성하는 OpenAPIGenerator 및 SwaggerUI와 같은 오픈 소스 도구 집합입니다.
- OpenAPI 사양: 컨트롤러 및 모델 내의 XML 및 특성 주석을 기반으로 API의 기능을 설명하는 문서입니다.
ASP.NET OpenAPI 및 NSwag를 사용하는 방법에 대한 자세한 내용은 Swagger/OpenAPI를 사용하는 ASP.NET Core Web API 설명서를 참조하세요.
Swagger 도구 설치
다음 명령을 실행합니다.
dotnet add package NSwag.AspNetCore
이전 명령은 Swagger 문서 및 UI를 생성하는 도구가 포함된 NSwag.AspNetCore 패키지를 추가합니다.
Swagger 미들웨어 구성
Program.cs 맨 위에 다음
using
문을 추가합니다.using NSwag.AspNetCore;
줄에 정의되기 전에
app
강조 표시된 다음 코드를 추가합니다.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();
위의 코드에서:
builder.Services.AddEndpointsApiExplorer();
: HTTP API에 대한 메타데이터를 제공하는 서비스인 API 탐색기를 사용하도록 설정합니다. API 탐색기는 Swagger에서 Swagger 문서를 생성하는 데 사용됩니다.builder.Services.AddOpenApiDocument(config => {...});
: Swagger OpenAPI 문서 생성기를 애플리케이션 서비스에 추가하고 해당 제목 및 버전과 같은 API에 대한 자세한 정보를 제공하도록 구성합니다. 보다 강력한 API 세부 정보를 제공하는 방법에 대한 자세한 내용은 NSwag 및 ASP.NET Core 시작을 참조하세요.다음 강조 표시된 코드를 줄에 정의한 후
app
다음 줄에 추가합니다.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"; }); }
이전 코드를 사용하면 Swagger 미들웨어가 생성된 JSON 문서 및 Swagger UI를 제공할 수 있습니다. Swagger는 개발 환경에서만 사용할 수 있습니다. 프로덕션 환경에서 Swagger를 사용하도록 설정하면 API의 구조 및 구현에 대한 잠재적으로 중요한 세부 정보가 노출될 수 있습니다.
데이터 게시 테스트
Program.cs
에서 다음 코드는 HTTP POST 엔드포인트 /todoitems
를 만들어 메모리 내 데이터베이스에 데이터를 추가합니다.
app.MapPost("/todoitems", async (Todo todo, TodoDb db) =>
{
db.Todos.Add(todo);
await db.SaveChangesAsync();
return Results.Created($"/todoitems/{todo.Id}", todo);
});
앱을 실행합니다. 더 이상 /
엔드포인트가 없으므로 브라우저에 404 오류가 표시됩니다.
POST 엔드포인트는 앱에 데이터를 추가하는 데 사용됩니다.
앱이 계속 실행 중인 상태에서 브라우저에서 Swagger에서 생성된 API 테스트 페이지를 표시하도록
https://localhost:<port>/swagger
이동합니다.Swagger API 테스트 페이지에서 Post /todoitems>Try it out을 선택합니다.
요청 본문 필드에는 API에 대한 매개 변수를 반영하는 생성된 예제 형식이 포함되어 있습니다.
요청 본문에서 선택 사항을
id
지정하지 않고 할 일 항목에 대한 JSON을 입력합니다.{ "name":"walk dog", "isComplete":true }
실행을 선택합니다.
Swagger는 실행 단추 아래에 응답 창을 제공합니다.
몇 가지 유용한 세부 정보를 확인합니다.
- cURL: Swagger는 Unix/Linux 구문의 예제 cURL 명령을 제공합니다. 이 명령은 Windows용 Git의 Git Bash를 포함하여 Unix/Linux 구문을 사용하는 bash 셸을 사용하여 명령줄에서 실행할 수 있습니다.
- 요청 URL: API 호출에 대한 Swagger UI의 JavaScript 코드에서 수행한 HTTP 요청의 간소화된 표현입니다. 실제 요청에는 헤더, 쿼리 매개 변수 및 요청 본문과 같은 세부 정보가 포함될 수 있습니다.
- 서버 응답: 응답 본문 및 헤더를 포함합니다. 응답 본문은 설정된 것을
id
보여 줍니다1
. - 응답 코드: 요청이 성공적으로 처리되고 새 리소스가 생성되었음을 나타내는 201
HTTP
상태 코드가 반환되었습니다.
GET 엔드포인트 검사
샘플 앱은 MapGet
을 호출하여 여러 GET 엔드포인트를 구현합니다.
API | 설명 | 요청 본문 | 응답 본문 |
---|---|---|---|
GET /todoitems |
할 일 항목 모두 가져오기 | None | 할 일 항목의 배열 |
GET /todoitems/complete |
완성된 모든 할 일 항목 가져오기 | None | 할 일 항목의 배열 |
GET /todoitems/{id} |
ID로 항목 가져오기 | None | 할 일 항목 |
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());
GET 엔드포인트 테스트
브라우저 또는 Swagger에서 엔드포인트를 호출하여 앱을 테스트합니다.
Swagger에서 GET /todoitems를 선택하여 실행해 보세요>.>
또는 URI
http://localhost:<port>/todoitems
를 입력하여 브라우저에서 GET /todoitems를 호출합니다. 예를 들어http://localhost:5001/todoitems
GET /todoitems
를 호출하면 다음과 비슷한 응답이 생성됩니다.
[
{
"id": 1,
"name": "walk dog",
"isComplete": true
}
]
Swagger에서 GET /todoitems/{id}를 호출하여 특정 ID에서 데이터를 반환합니다.
- GET /todoitems Try it out을>선택합니다.
- ID 필드를
1
설정하고 실행을 선택합니다.
또는 URI
https://localhost:<port>/todoitems/1
를 입력하여 브라우저에서 GET /todoitems를 호출합니다. 예를 들면 다음과 같습니다.https://localhost:5001/todoitems/1
응답은 다음과 비슷합니다.
{ "id": 1, "name": "walk dog", "isComplete": true }
이 앱은 메모리 내 데이터베이스를 사용합니다. 앱이 다시 시작되면 GET 요청이 데이터를 반환하지 않습니다. 데이터가 반환되지 않으면 POST 데이터를 앱에 게시하고 GET 요청을 다시 시도합니다.
반환 값
ASP.NET Core는 자동으로 JSON에 개체를 직렬화하고 JSON을 응답 메시지의 본문에 기록합니다. 이 반환 형식의 응답 코드는 200 OK이며 처리되지 않은 예외가 없다고 가정합니다. 처리되지 않은 예외는 5xx 오류로 변환됩니다.
반환 형식은 다양한 HTTP 상태 코드를 나타낼 수 있습니다. 예를 들어 GET /todoitems/{id}
은 두 가지 상태 값을 반환할 수 있습니다.
- 요청된 ID와 일치하는 항목이 없는 경우 메서드에서 404 상태 NotFound 오류 코드를 반환합니다.
- 그렇지 않으면 메서드가 JSON 응답 본문에서 200을 반환합니다.
item
을 반환하면 HTTP 200 응답이 발생합니다.
PUT 엔드포인트 검사
샘플 앱은 MapPut
을 사용하여 단일 PUT 엔드포인트를 구현합니다.
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();
});
이 메서드는 HTTP PUT을 사용한다는 점을 제외하고 MapPost
메서드와 비슷합니다. 성공적인 응답은 204(콘텐츠 없음)를 반환합니다. HTTP 사양에 따라 PUT 요청의 경우 클라이언트는 변경 내용만이 아니라 전체 업데이트된 엔터티를 보내야 합니다. 부분 업데이트를 지원하려면 HTTP PATCH를 사용합니다.
PUT 엔드포인트 테스트
이 샘플은 앱이 시작될 때마다 초기화되어야 하는 메모리 내 데이터베이스를 사용합니다. PUT 호출을 실행하기 전에 데이터베이스에 항목이 있어야 합니다. GET을 호출하여 PUT 호출을 실행하기 전에 데이터베이스에 항목이 있는지 확인합니다.
할 일 항목을 Id = 1
업데이트하고 이름을 "feed fish"
.로 설정합니다.
Swagger를 사용하여 PUT 요청을 보냅니다.
/todoitems/{id}>놓기를 선택합니다.
ID 필드를 .로
1
설정합니다.요청 본문을 다음 JSON으로 설정합니다.
{ "name": "feed fish", "isComplete": false }
실행을 선택합니다.
DELETE 엔드포인트 검사 및 테스트
샘플 앱은 MapDelete
를 사용하여 단일 DELETE 엔드포인트를 구현합니다.
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();
});
Swagger를 사용하여 DELETE 요청을 보냅니다.
DELETE /todoitems/{id}>사용해 보세요.
ID 필드를
1
설정하고 실행을 선택합니다.DELETE 요청이 앱으로 전송되고 응답 창에 응답이 표시됩니다. 응답 본문이 비어 있고 서버 응답 상태 코드는 204입니다.
과도한 게시 방지
현재 샘플 앱은 전체 Todo
개체를 공개합니다. 프로덕션 앱 프로덕션 애플리케이션에서 모델의 하위 집합은 입력 및 반환될 수 있는 데이터를 제한하는 데 자주 사용됩니다. 이 동작에는 여러 가지 이유가 있으며, 보안이 주요 이유 중 하나입니다. 일반적으로 모델의 하위 집합을 DTO(데이터 전송 개체), 입력 모델 또는 뷰 모델이라고 합니다. 이 문서에서는 DTO를 사용합니다.
DTO를 사용하여 다음을 수행할 수 있습니다.
- 과도한 게시를 방지합니다.
- 클라이언트에서 볼 수 없는 속성을 숨깁니다.
- 페이로드 크기를 줄이기 위해 일부 속성을 생략합니다.
- 중첩된 개체를 포함하는 개체 그래프를 평면화합니다. 클라이언트에는 평면화된 개체 그래프가 더 편리할 수 있습니다.
DTO 방법을 설명하려면 비밀 필드를 포함하도록 Todo
클래스를 업데이트합니다.
public class Todo
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
public string? Secret { get; set; }
}
이 앱에서는 숨겨진 필드를 숨겨야 하지만, 관리 앱은 숨겨진 필드를 공개할 수 있습니다.
비밀 필드를 게시하고 가져올 수 있는지 확인합니다.
다음 코드를 사용하여 TodoItemDTO.cs
라는 파일을 만듭니다.
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);
}
이 DTO 모델을 사용하려면 파일의 Program.cs
내용을 다음 코드로 바꿉니다.
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>();
}
비밀 필드를 제외한 모든 필드를 게시하고 가져올 수 있는지 확인합니다.
최소 API 테스트
최소 API 앱 테스트의 예는 이 GitHub 샘플을 참조하세요.
Azure에 게시
Azure에 배포에 대한 자세한 내용은 빠른 시작: ASP.NET 웹앱 배포를 참조하세요.
추가 리소스
최소 API는 최소한의 종속성으로 HTTP API를 만들도록 설계되었습니다. ASP.NET Core에 최소 파일, 기능 및 종속성만 포함하려는 마이크로 서비스 및 앱에 이상적입니다.
이 자습서에서는 ASP.NET Core를 사용한 최소 API를 빌드하는 기본 사항을 설명합니다. ASP.NET Core에서 API를 만드는 또 다른 방법은 컨트롤러를 사용하는 것입니다. 최소 API와 컨트롤러 기반 API 중에서 선택하는 방법에 대한 도움말은 API 개요를 참조하세요. 더 많은 기능이 포함된 컨트롤러를 기반으로 API 프로젝트를 만드는 방법에 대한 자습서는 웹 API 만들기를 참조하세요.
개요
이 자습서에서는 다음 API를 만듭니다.
API | 설명 | 요청 본문 | 응답 본문 |
---|---|---|---|
GET /todoitems |
할 일 항목 모두 가져오기 | None | 할 일 항목의 배열 |
GET /todoitems/complete |
완성된 할 일 항목 가져오기 | None | 할 일 항목의 배열 |
GET /todoitems/{id} |
ID로 항목 가져오기 | None | 할 일 항목 |
POST /todoitems |
새 항목 추가 | 할 일 항목 | 할 일 항목 |
PUT /todoitems/{id} |
기존 항목 업데이트 | 할 일 항목 | None |
DELETE /todoitems/{id} |
항목 삭제 | None | None |
필수 조건
ASP.NET 및 웹 개발 워크로드가 있는 Visual Studio 2022
API 프로젝트 만들기
Visual Studio 2022를 시작하고 새 프로젝트 만들기를 선택합니다.
새 프로젝트 만들기 대화 상자에서 다음을 수행합니다.
- 템플릿 검색 검색 상자에
Empty
를 입력합니다. - ASP.NET Core Empty 템플릿을 선택하고 다음을 선택합니다.
- 템플릿 검색 검색 상자에
프로젝트 이름을 TodoApi로 지정하고 다음을 선택합니다.
추가 정보 대화 상자에서 다음을 수행합니다.
- .NET 8.0(장기 지원) 선택
- 최상위 문 사용 안 함 선택 취소
- 만들기를 선택합니다.
코드 검사
Program.cs
파일에는 다음 코드가 포함되어 있습니다.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
앞의 코드가 하는 역할은 다음과 같습니다.
- 미리 구성된 기본값을 사용하여 WebApplicationBuilder 및 WebApplication을 만듭니다.
Hello World!
를 반환하는 HTTP GET 엔드포인트/
를 만듭니다.
앱 실행
Ctrl+F5를 눌러 디버거 없이 실행합니다.
Visual Studio는 다음 대화 상자를 표시합니다.
IIS Express SSL 인증서를 신뢰하는 경우 예를 선택합니다.
다음 대화 상자가 표시됩니다.
개발 인증서를 신뢰하는 데 동의하는 경우 예를 선택합니다.
Firefox 브라우저를 신뢰하는 방법에 대한 자세한 내용은 Firefox SEC_ERROR_INADEQUATE_KEY_USAGE 인증서 오류를 참조하세요.
Visual Studio에서 Kestrel 웹 서버를 시작하고 브라우저 창을 엽니다.
Hello World!
가 브라우저에 표시됩니다. Program.cs
파일에는 최소이지만 완전한 앱이 포함되어 있습니다.
브라우저 창을 닫습니다.
NuGet 패키지 추가
이 자습서에서 사용되는 데이터베이스 및 진단을 지원하려면 NuGet 패키지를 추가해야 합니다.
- 도구 메뉴에서 NuGet 패키지 관리자 > 솔루션용 NuGet 패키지 관리를 선택합니다.
- 찾아보기 탭을 선택합니다.
- 검색 상자에 Microsoft.EntityFrameworkCore.InMemory를 입력한 다음
Microsoft.EntityFrameworkCore.InMemory
를 선택합니다. - 오른쪽 창에서 프로젝트 확인란을 선택하고 설치를 선택합니다.
- 이전 지침에 따라
Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
패키지를 추가합니다.
모델 및 데이터베이스 컨텍스트 클래스
- 프로젝트 폴더에서 다음 코드가 포함된
Todo.cs
라는 파일을 만듭니다.
public class Todo
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
}
앞의 코드는 이 앱에 대한 모델을 만듭니다. 모델은 앱에서 관리하는 데이터를 나타내는 일련의 클래스입니다.
- 다음 코드를 사용하여
TodoDb.cs
라는 파일을 만듭니다.
using Microsoft.EntityFrameworkCore;
class TodoDb : DbContext
{
public TodoDb(DbContextOptions<TodoDb> options)
: base(options) { }
public DbSet<Todo> Todos => Set<Todo>();
}
앞의 코드는 데이터 모델에 대한 Entity Framework 기능을 조정하는 기본 클래스인 데이터베이스 컨텍스트를 정의합니다. 이 클래스는 Microsoft.EntityFrameworkCore.DbContext 클래스에서 파생됩니다.
API 코드 추가
Program.cs
파일의 내용을 다음 코드로 바꿉니다.
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();
강조 표시된 다음 코드는 DI(종속성 주입) 컨테이너에 데이터베이스 컨텍스트를 추가하고 데이터베이스 관련 예외를 표시할 수 있도록 합니다.
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();
DI 컨테이너는 데이터베이스 컨텍스트 및 기타 서비스에 대한 액세스를 제공합니다.
이 자습서에서는 엔드포인트 탐색기 및 .http 파일을 사용하여 API를 테스트합니다.
데이터 게시 테스트
Program.cs
에서 다음 코드는 HTTP POST 엔드포인트 /todoitems
를 만들어 메모리 내 데이터베이스에 데이터를 추가합니다.
app.MapPost("/todoitems", async (Todo todo, TodoDb db) =>
{
db.Todos.Add(todo);
await db.SaveChangesAsync();
return Results.Created($"/todoitems/{todo.Id}", todo);
});
앱을 실행합니다. 더 이상 /
엔드포인트가 없으므로 브라우저에 404 오류가 표시됩니다.
POST 엔드포인트는 앱에 데이터를 추가하는 데 사용됩니다.
다른 Windows>엔드포인트 탐색기 보기를>선택합니다.
POST 엔드포인트를 마우스 오른쪽 단추로 클릭하고 요청 생성을 선택합니다.
다음 예제와 유사한 내용이 포함된 새 파일이 프로젝트 폴더
TodoApi.http
에 만들어집니다.@TodoApi_HostAddress = https://localhost:7031 Post {{TodoApi_HostAddress}}/todoitems ###
- 첫 번째 줄은 모든 엔드포인트에 사용되는 변수를 만듭니다.
- 다음 줄은 POST 요청을 정의합니다.
- 삼중 해시 태그(
###
) 줄은 요청 구분 기호입니다. 이는 다른 요청에 대한 것입니다.
POST 요청에는 헤더와 본문이 필요합니다. 요청의 해당 부분을 정의하려면 POST 요청 줄 바로 다음에 다음 줄을 추가합니다.
Content-Type: application/json { "name":"walk dog", "isComplete":true }
앞의 코드는 Content-Type 헤더와 JSON 요청 본문을 추가합니다. 이제 TodoApi.http 파일은 다음 예제와 비슷하지만 포트 번호가 있습니다.
@TodoApi_HostAddress = https://localhost:7057 Post {{TodoApi_HostAddress}}/todoitems Content-Type: application/json { "name":"walk dog", "isComplete":true } ###
앱을 실행합니다.
요청 줄 위에
POST
있는 요청 보내기 링크를 선택합니다.POST 요청이 앱으로 전송되고 응답 창에 응답이 표시됩니다.
GET 엔드포인트 검사
샘플 앱은 MapGet
을 호출하여 여러 GET 엔드포인트를 구현합니다.
API | 설명 | 요청 본문 | 응답 본문 |
---|---|---|---|
GET /todoitems |
할 일 항목 모두 가져오기 | None | 할 일 항목의 배열 |
GET /todoitems/complete |
완성된 모든 할 일 항목 가져오기 | None | 할 일 항목의 배열 |
GET /todoitems/{id} |
ID로 항목 가져오기 | None | 할 일 항목 |
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());
GET 엔드포인트 테스트
브라우저에서 엔드포인트를 GET
호출하거나 엔드포인트 탐색기를 사용하여 앱을 테스트합니다. 다음 단계는 엔드포인트 탐색기에 대한 것입니다.
엔드포인트 탐색기에서 첫 번째 GET 엔드포인트를 마우스 오른쪽 단추로 클릭하고 요청 생성을 선택합니다.
다음 콘텐츠가 파일에 추가
TodoApi.http
됩니다.Get {{TodoApi_HostAddress}}/todoitems ###
새
GET
요청 줄 위에 있는 요청 보내기 링크를 선택합니다.GET 요청이 앱으로 전송되고 응답 창에 응답이 표시됩니다.
응답 본문은 다음 JSON과 유사합니다.
[ { "id": 1, "name": "walk dog", "isComplete": true } ]
엔드포인트 탐색기에서 GET 엔드포인트를 마우스 오른쪽 단추로
/todoitems/{id}
클릭하고 요청 생성을 선택합니다. 다음 콘텐츠가 파일에 추가TodoApi.http
됩니다.GET {{TodoApi_HostAddress}}/todoitems/{id} ###
{id}
를1
로 교체합니다.새 GET 요청 줄 위에 있는 요청 보내기 링크를 선택합니다.
GET 요청이 앱으로 전송되고 응답 창에 응답이 표시됩니다.
응답 본문은 다음 JSON과 유사합니다.
{ "id": 1, "name": "walk dog", "isComplete": true }
이 앱은 메모리 내 데이터베이스를 사용합니다. 앱이 다시 시작되면 GET 요청이 데이터를 반환하지 않습니다. 데이터가 반환되지 않으면 POST 데이터를 앱에 게시하고 GET 요청을 다시 시도합니다.
반환 값
ASP.NET Core는 자동으로 JSON에 개체를 직렬화하고 JSON을 응답 메시지의 본문에 기록합니다. 이 반환 형식의 응답 코드는 200 OK이며 처리되지 않은 예외가 없다고 가정합니다. 처리되지 않은 예외는 5xx 오류로 변환됩니다.
반환 형식은 다양한 HTTP 상태 코드를 나타낼 수 있습니다. 예를 들어 GET /todoitems/{id}
은 두 가지 상태 값을 반환할 수 있습니다.
- 요청된 ID와 일치하는 항목이 없는 경우 메서드에서 404 상태 NotFound 오류 코드를 반환합니다.
- 그렇지 않으면 메서드가 JSON 응답 본문에서 200을 반환합니다.
item
을 반환하면 HTTP 200 응답이 발생합니다.
PUT 엔드포인트 검사
샘플 앱은 MapPut
을 사용하여 단일 PUT 엔드포인트를 구현합니다.
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();
});
이 메서드는 HTTP PUT을 사용한다는 점을 제외하고 MapPost
메서드와 비슷합니다. 성공적인 응답은 204(콘텐츠 없음)를 반환합니다. HTTP 사양에 따라 PUT 요청의 경우 클라이언트는 변경 내용만이 아니라 전체 업데이트된 엔터티를 보내야 합니다. 부분 업데이트를 지원하려면 HTTP PATCH를 사용합니다.
PUT 엔드포인트 테스트
이 샘플은 앱이 시작될 때마다 초기화되어야 하는 메모리 내 데이터베이스를 사용합니다. PUT 호출을 실행하기 전에 데이터베이스에 항목이 있어야 합니다. GET을 호출하여 PUT 호출을 실행하기 전에 데이터베이스에 항목이 있는지 확인합니다.
할 일 항목을 Id = 1
업데이트하고 이름을 "feed fish"
.로 설정합니다.
엔드포인트 탐색기에서 PUT 엔드포인트를 마우스 오른쪽 단추로 클릭하고 요청 생성을 선택합니다.
다음 콘텐츠가 파일에 추가
TodoApi.http
됩니다.Put {{TodoApi_HostAddress}}/todoitems/{id} ###
PUT 요청 줄
{id}
1
에서 .PUT 요청 줄 바로 다음에 다음 줄을 추가합니다.
Content-Type: application/json { "name": "feed fish", "isComplete": false }
앞의 코드는 Content-Type 헤더와 JSON 요청 본문을 추가합니다.
새 PUT 요청 줄 위에 있는 요청 보내기 링크를 선택합니다.
PUT 요청이 앱으로 전송되고 응답 창에 응답이 표시됩니다. 응답 본문이 비어 있고 상태 코드는 204입니다.
DELETE 엔드포인트 검사 및 테스트
샘플 앱은 MapDelete
를 사용하여 단일 DELETE 엔드포인트를 구현합니다.
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();
});
엔드포인트 탐색기에서 DELETE 엔드포인트를 마우스 오른쪽 단추로 클릭하고 요청 생성을 선택합니다.
DELETE 요청이 에 추가
TodoApi.http
됩니다.DELETE 요청 줄에서 .로 바
1
꿉{id}
다. DELETE 요청은 다음 예제와 같습니다.DELETE {{TodoApi_HostAddress}}/todoitems/1 ###
DELETE 요청에 대한 요청 보내기 링크를 선택합니다.
DELETE 요청이 앱으로 전송되고 응답 창에 응답이 표시됩니다. 응답 본문이 비어 있고 상태 코드는 204입니다.
MapGroup API 사용
샘플 앱 코드는 엔드포인트를 설정할 때마다 todoitems
URL 접두사를 반복합니다. API에는 공통 URL 접두사를 사용하는 엔드포인트 그룹이 있는 경우가 많으며 MapGroup 메서드를 사용하여 이러한 그룹을 구성할 수 있습니다. 반복 코드를 줄이고 RequireAuthorization 및 WithMetadata와 같은 메서드에 대한 단일 호출로 전체 엔드포인트 그룹을 사용자 지정할 수 있습니다.
Program.cs
의 내용을 다음 코드로 바꿉니다.
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();
위의 코드에는 다음과 같은 변경 내용이 있습니다.
- URL 접두사
/todoitems
을 사용하여 그룹을 설정하도록var todoItems = app.MapGroup("/todoitems");
을 추가합니다. - 모든
app.Map<HttpVerb>
메서드를todoItems.Map<HttpVerb>
로 변경합니다. Map<HttpVerb>
메서드 호출에서 URL 접두사/todoitems
를 제거합니다.
엔드포인트를 테스트하여 동일하게 작동하는지 확인합니다.
TypedResults API 사용
TypedResults 테스트 용이성 및 엔드포인트를 설명하기 위해 OpenAPI에 대한 응답 형식 메타데이터를 자동으로 반환하는 등 여러 가지 이점이 있는 대신 Results 반환합니다. 자세한 내용은 TypedResults 및 결과를 참조하세요.
Map<HttpVerb>
메서드는 람다를 사용하는 대신 경로 처리기 메서드를 호출할 수 있습니다. 예제를 보려면 다음 코드로 Program.cs를 업데이트합니다.
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();
}
이제 Map<HttpVerb>
코드는 람다 대신 메서드를 호출합니다.
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);
이러한 메서드는 IResult를 구현하고 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();
}
단위 테스트는 이러한 메서드를 호출하고 올바른 형식을 반환하는지 테스트할 수 있습니다. 예를 들어 메서드가 GetAllTodos
인 경우:
static async Task<IResult> GetAllTodos(TodoDb db)
{
return TypedResults.Ok(await db.Todos.ToArrayAsync());
}
단위 테스트 코드는 Ok<Todo[]> 형식의 개체가 처리기 메서드에서 반환되는지 확인할 수 있습니다. 예시:
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);
}
과도한 게시 방지
현재 샘플 앱은 전체 Todo
개체를 공개합니다. 프로덕션 앱 프로덕션 애플리케이션에서 모델의 하위 집합은 입력 및 반환될 수 있는 데이터를 제한하는 데 자주 사용됩니다. 이 동작에는 여러 가지 이유가 있으며, 보안이 주요 이유 중 하나입니다. 일반적으로 모델의 하위 집합을 DTO(데이터 전송 개체), 입력 모델 또는 뷰 모델이라고 합니다. 이 문서에서는 DTO를 사용합니다.
DTO를 사용하여 다음을 수행할 수 있습니다.
- 과도한 게시를 방지합니다.
- 클라이언트에서 볼 수 없는 속성을 숨깁니다.
- 페이로드 크기를 줄이기 위해 일부 속성을 생략합니다.
- 중첩된 개체를 포함하는 개체 그래프를 평면화합니다. 클라이언트에는 평면화된 개체 그래프가 더 편리할 수 있습니다.
DTO 방법을 설명하려면 비밀 필드를 포함하도록 Todo
클래스를 업데이트합니다.
public class Todo
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
public string? Secret { get; set; }
}
이 앱에서는 숨겨진 필드를 숨겨야 하지만, 관리 앱은 숨겨진 필드를 공개할 수 있습니다.
비밀 필드를 게시하고 가져올 수 있는지 확인합니다.
다음 코드를 사용하여 TodoItemDTO.cs
라는 파일을 만듭니다.
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);
}
이 DTO 모델을 사용하려면 파일의 Program.cs
내용을 다음 코드로 바꿉니다.
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();
}
비밀 필드를 제외한 모든 필드를 게시하고 가져올 수 있는지 확인합니다.
완료된 샘플 문제 해결
해결할 수 없는 문제가 발생한 경우 완료된 프로젝트와 코드를 비교합니다. 완료된 프로젝트를 보거나 다운로드합니다(다운로드 방법).
다음 단계
- JSON serialization 옵션을 구성합니다.
- 오류 및 예외 처리: 개발자 예외 페이지는 최소 API 앱에 대해 개발 환경에서 기본적으로 사용하도록 설정됩니다. 오류 및 예외를 처리하는 방법에 대한 자세한 내용은 ASP.NET Core API에서 오류 처리를 참조하세요.
- 최소 API 앱 테스트의 예는 이 GitHub 샘플을 참조하세요.
- 최소 API에서 OpenAPI를 지원합니다.
- 빠른 시작: Azure에 게시합니다.
- ASP.NET Core 최소 API 구성
자세한 정보
ASP.NET Core