자습서: ASP.NET Core를 사용하여 웹 API 만들기

작성자: Rick AndersonKirk Larkin

이 자습서에서는 ASP.NET Core를 사용하여 웹 API를 빌드하는 작업의 기본 사항을 설명합니다.

이 자습서에서는 다음과 같은 작업을 수행하는 방법을 살펴봅니다.

  • 웹 API 프로젝트를 만듭니다.
  • 모델 클래스와 데이터베이스 컨텍스트를 추가합니다.
  • CRUD 메서드로 컨트롤러를 스캐폴드합니다.
  • 라우팅, URL 경로 및 반환 값을 구성합니다.
  • http-repl을 사용하여 웹 API를 호출합니다.

과정을 마치면 웹 API를 통해 데이터베이스에 저장된 “할 일” 항목을 관리할 수 있습니다.

개요

이 자습서에서는 다음 API를 만듭니다.

API 설명 요청 본문 응답 본문
GET /api/todoitems 할 일 항목 모두 가져오기 없음 할 일 항목의 배열
GET /api/todoitems/{id} ID로 항목 가져오기 없음 할 일 항목
POST /api/todoitems 새 항목 추가 할 일 항목 할 일 항목
PUT /api/todoitems/{id} 기존 항목 업데이트 할 일 항목 없음
DELETE /api/todoitems/{id}     항목 삭제 없음 없음

다음 다이어그램에서는 앱의 디자인을 보여줍니다.

The client is represented by a box on the left. It submits a request and receives a response from the application, a box drawn on the right. Within the application box, three boxes represent the controller, the model, and the data access layer. The request comes into the application's controller, and read/write operations occur between the controller and the data access layer. The model is serialized and returned to the client in the response.

사전 요구 사항

웹 프로젝트 만들기

  • 파일 메뉴에서 새로 만들기>프로젝트를 선택합니다.
  • 검색 상자에 Web API를 입력합니다.
  • ASP.NET Core Web API 템플릿을 선택하고 다음을 선택합니다.
  • 새 프로젝트 대화 상자 구성에서 TodoApi 프로젝트 이름을 지정하고 다음을 선택합니다.
  • 추가 정보 대화 상자에서 다음을 수행합니다.
    • 프레임워크.Net 6.0(장기 지원)인지 확인합니다.
    • 컨트롤러 사용(최소 API를 사용하려면 선택 취소) 확인란을 선택합니다.
    • 만들기를 선택합니다.

참고

.NET 앱에 패키지를 추가하는 지침은 패키지 사용 워크플로(NuGet 설명서)에서 패키지 설치 및 관리 아래의 문서를 참조하세요. NuGet.org에서 올바른 패키지 버전을 확인합니다.

프로젝트 테스트

프로젝트 템플릿은 Swagger를 지원하는 WeatherForecast API를 만듭니다.

Ctrl+F5를 눌러 디버거 없이 실행합니다.

프로젝트가 SSL을 사용하도록 아직 구성되지 않은 경우 Visual Studio에 다음 대화 상자가 표시됩니다.

This project is configured to use SSL. To avoid SSL warnings in the browser you can choose to trust the self-signed certificate that IIS Express has generated. Would you like to trust the IIS Express SSL certificate?

IIS Express SSL 인증서를 신뢰하는 경우 를 선택합니다.

다음 대화 상자가 표시됩니다.

Security warning dialog

개발 인증서를 신뢰하는 데 동의하는 경우 를 선택합니다.

Firefox 브라우저를 신뢰하는 방법에 대한 자세한 내용은 Firefox SEC_ERROR_INADEQUATE_KEY_USAGE 인증서 오류를 참조하세요.

Visual Studio가 기본 브라우저를 시작하고 https://localhost:<port>/swagger/index.html로 이동합니다. 여기서 <port>는 임의로 선택된 포트 번호입니다.

Swagger 페이지 /swagger/index.html이 표시됩니다. GET>사용해 보기>실행을 선택합니다. 페이지에 다음이 표시됩니다.

  • WeatherForecast API를 테스트할 Curl 명령
  • WeatherForecast API를 테스트할 URL
  • 응답 코드, 본문 및 헤더
  • 미디어 유형과 예제 값 및 스키마가 포함된 드롭다운 목록 상자

Swagger 페이지가 표시되지 않는 경우 이 GitHub 이슈를 참조하세요.

Swagger는 웹 API에 유용한 설명서 및 도움말 페이지를 생성하는 데 사용됩니다. 이 자습서에서는 웹 API 만들기에 대해 집중적으로 다룹니다. Swagger에 대한 자세한 내용은 Swagger/OpenAPI를 사용한 ASP.NET Core Web API 설명서를 참조하세요.

브라우저에서 요청 URL을 복사하여 붙여넣습니다. https://localhost:<port>/weatherforecast

다음 예제와 비슷한 JSON이 반환됩니다.

[
    {
        "date": "2019-07-16T19:04:05.7257911-06:00",
        "temperatureC": 52,
        "temperatureF": 125,
        "summary": "Mild"
    },
    {
        "date": "2019-07-17T19:04:05.7258461-06:00",
        "temperatureC": 36,
        "temperatureF": 96,
        "summary": "Warm"
    },
    {
        "date": "2019-07-18T19:04:05.7258467-06:00",
        "temperatureC": 39,
        "temperatureF": 102,
        "summary": "Cool"
    },
    {
        "date": "2019-07-19T19:04:05.7258471-06:00",
        "temperatureC": 10,
        "temperatureF": 49,
        "summary": "Bracing"
    },
    {
        "date": "2019-07-20T19:04:05.7258474-06:00",
        "temperatureC": -1,
        "temperatureF": 31,
        "summary": "Chilly"
    }
]

launchUrl 업데이트

Properties\launchSettings.json에서 launchUrl"swagger"에서 "api/todoitems"로 업데이트합니다.

"launchUrl": "api/todoitems",

Swagger가 제거되므로 위 태그는 다음 섹션에 추가된 컨트롤러의 GET 메서드로 시작되는 URL을 변경합니다.

모델 클래스 추가

모델은 앱에서 관리하는 데이터를 나타내는 일련의 클래스입니다. 이 앱에 대한 모델은 단일 TodoItem 클래스입니다.

  • 솔루션 탐색기에서 프로젝트를 마우스 오른쪽 단추로 클릭합니다. 추가>새 폴더를 선택합니다. 폴더 이름을 Models서 무료 평가판 계정을 등록할 수 있습니다.

  • Models 폴더를 마우스 오른쪽 단추로 클릭하고 추가>클래스를 선택합니다. 클래스 이름을 TodoItem으로 지정하고 추가를 선택합니다.

  • 템플릿 코드를 다음으로 바꿉니다.

namespace TodoApi.Models
{
    public class TodoItem
    {
        public long Id { get; set; }
        public string? Name { get; set; }
        public bool IsComplete { get; set; }
    }
}

Id 속성은 관계형 데이터베이스에서 고유 키로 작동합니다.

모델 클래스는 프로젝트의 어디로든 이동할 수 있지만 규칙에 따라 Models 폴더를 사용합니다.

데이터베이스 컨텍스트 추가

데이터베이스 컨텍스트는 데이터 모델에 맞게 Entity Framework 기능을 조정하는 주 클래스입니다. Microsoft.EntityFrameworkCore.DbContext 클래스에서 파생시키는 방식으로 이 클래스를 만듭니다.

NuGet 패키지 추가

  • 도구 메뉴에서 NuGet 패키지 관리자 > 솔루션용 NuGet 패키지 관리를 선택합니다.
  • 찾아보기 탭을 선택한 다음, 검색 상자에 Microsoft.EntityFrameworkCore.InMemory를 입력합니다.
  • 왼쪽 창에서 Microsoft.EntityFrameworkCore.InMemory를 선택합니다.
  • 오른쪽 창에서 프로젝트 확인란을 선택하고 설치를 선택합니다.

TodoContext 데이터베이스 컨텍스트 추가

  • Models 폴더를 마우스 오른쪽 단추로 클릭하고 추가>클래스를 선택합니다. 클래스 이름을 TodoContext로 지정하고 추가를 클릭합니다.
  • 다음 코드를 입력합니다.

    using Microsoft.EntityFrameworkCore;
    using System.Diagnostics.CodeAnalysis;
    
    namespace TodoApi.Models
    {
        public class TodoContext : DbContext
        {
            public TodoContext(DbContextOptions<TodoContext> options)
                : base(options)
            {
            }
    
            public DbSet<TodoItem> TodoItems { get; set; } = null!;
        }
    }
    

데이터베이스 컨텍스트 등록

ASP.NET Core에서는 DB 컨텍스트와 같은 서비스를 DI(종속성 주입) 컨테이너에 등록해야 합니다. 컨테이너는 컨트롤러에 서비스를 제공합니다.

다음 코드를 사용하여 Program.cs을 업데이트합니다.

using Microsoft.EntityFrameworkCore;
using TodoApi.Models;


var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

builder.Services.AddControllers();
builder.Services.AddDbContext<TodoContext>(opt =>
    opt.UseInMemoryDatabase("TodoList"));
//builder.Services.AddSwaggerGen(c =>
//{
//    c.SwaggerDoc("v1", new() { Title = "TodoApi", Version = "v1" });
//});

var app = builder.Build();

// Configure the HTTP request pipeline.
if (builder.Environment.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
    //app.UseSwagger();
    //app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "TodoApi v1"));
}

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

위의 코드는

  • Swagger 호출을 제거합니다.
  • 사용하지 않는 using 지시문을 제거합니다.
  • DI 컨테이너에 데이터베이스 컨텍스트를 추가합니다.
  • 데이터베이스 컨텍스트가 메모리 내 데이터베이스를 사용하도록 지정합니다.

컨트롤러 스캐폴드

  • Controllers 폴더를 마우스 오른쪽 단추로 클릭합니다.

  • 추가>스캐폴드 항목 새로 만들기를 선택합니다.

  • Entity Framework를 사용하며 동작이 포함된 API 컨트롤러를 선택하고 추가를 선택합니다.

  • Entity Framework를 사용하며 동작이 포함된 API 컨트롤러 추가 대화 상자에서:

    • 모델 클래스에서 TodoItem (TodoApi.Models) 을 선택합니다.
    • 데이터 컨텍스트 클래스에서 TodoContext (TodoApi.Models) 를 선택합니다.
    • 추가를 선택합니다.

    스캐폴딩 작업이 실패하는 경우 추가를 선택하여 스캐폴딩을 다시 시도합니다.

생성된 코드는:

  • [ApiController] 특성으로 클래스를 표시합니다. 이 특성은 컨트롤러가 웹 API 요청에 응답함을 나타냅니다. 이 특성이 사용하도록 설정하는 특정 동작에 대한 자세한 내용은 ASP.NET Core로 Web API 만들기를 참조하세요.
  • DI를 사용하여 데이터베이스 컨텍스트(TodoContext)를 컨트롤러에 삽입합니다. 컨트롤러의 각 CRUD 메서드에서 해당 데이터베이스 컨텍스트를 사용합니다.

ASP.NET Core 템플릿과 관련해서 다음 사항을 확인합니다.

  • 뷰가 있는 컨트롤러용 ASP.NET Core 템플릿의 경우, 경로 템플릿에 [action]이 포함됩니다.
  • API 컨트롤러용 ASP.NET Core 템플릿의 경우, 경로 템플릿에 [action]이 포함되지 않습니다.

[action] 토큰이 경로 템플릿에 없는 경우 경로에서 작업 이름이 제외됩니다. 즉, 일치하는 경로에서 작업과 연결된 메서드 이름이 사용되지 않습니다.

PostTodoItem 만들기 메서드 업데이트

nameof 연산자를 사용하도록 PostTodoItem의 return 문을 업데이트합니다.

[HttpPost]
public async Task<ActionResult<TodoItem>> PostTodoItem(TodoItem todoItem)
{
    _context.TodoItems.Add(todoItem);
    await _context.SaveChangesAsync();

    //return CreatedAtAction("GetTodoItem", new { id = todoItem.Id }, todoItem);
    return CreatedAtAction(nameof(GetTodoItem), new { id = todoItem.Id }, todoItem);
}

위의 코드는 [HttpPost] 특성으로 표시되는 HTTP POST 메서드입니다. 이 메서드는 HTTP 요청 본문에서 할 일 항목 값을 가져옵니다.

자세한 내용은 Http[동사] 특성을 사용한 특성 라우팅을 참조하세요.

CreatedAtAction 메서드는 다음 작업을 수행합니다.

  • 성공하면 HTTP 201 상태 코드를 반환합니다. HTTP 201은 서버에서 새 리소스를 만드는 HTTP POST 메서드의 표준 응답입니다.
  • 응답에 대한 위치 헤더를 추가합니다. Location 헤더는 새로 만들어진 할 일 항목의 URI를 지정합니다. 자세한 내용은 10.2.2 201 생성됨을 참조하세요.
  • Location 헤더의 URI를 만들려면 GetTodoItem 작업을 참조합니다. C# nameof 키워드는 CreatedAtAction 호출에서 작업 이름의 하드 코딩을 방지하는 데 사용됩니다.

http-repl 설치

이 자습서에서는 http-repl을 사용하여 웹 API를 테스트합니다.

  • 명령 프롬프트에서 다음 명령을 실행합니다.

    dotnet tool install -g Microsoft.dotnet-httprepl
    
  • .NET 6.0 SDK나 런타임이 설치되지 않은 경우 .NET 6.0 런타임을 설치합니다.

PostTodoItem 테스트

  • Ctrl+F5 키를 눌러 앱을 실행합니다.

  • 새 터미널 창을 열고 다음 명령을 실행합니다. 앱에서 다른 포트 번호를 사용하는 경우 httprepl 명령의 5001을 포트 번호로 대체합니다.

    httprepl https://localhost:5001/api/todoitems
    post -h Content-Type=application/json -c "{"name":"walk dog","isComplete":true}"
    

    다음은 명령의 출력 예입니다.

    HTTP/1.1 201 Created
    Content-Type: application/json; charset=utf-8
    Date: Tue, 07 Sep 2021 20:39:47 GMT
    Location: https://localhost:5001/api/TodoItems/1
    Server: Kestrel
    Transfer-Encoding: chunked
    
    {
      "id": 1,
      "name": "walk dog",
      "isComplete": true
    }
    

위치 헤더 URI 테스트

위치 헤더를 테스트하려면 위치 헤더를 복사하여 httprepl get 명령에 붙여넣습니다.

다음 예제에서는 아직 httprepl 세션에 있다고 가정합니다. 이전 httprepl 세션을 종료한 경우 다음 명령에서 connecthttprepl로 대체합니다.

connect https://localhost:5001/api/todoitems/1
get

다음은 명령의 출력 예입니다.

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Tue, 07 Sep 2021 20:48:10 GMT
Server: Kestrel
Transfer-Encoding: chunked

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

GET 메서드 검사

두 개의 GET 엔드포인트가 구현됩니다.

  • GET /api/todoitems
  • GET /api/todoitems/{id}

/api/todoitems/{id} 경로의 예를 살펴보았습니다. /api/todoitems 경로를 테스트합니다.

connect https://localhost:5001/api/todoitems
get

다음은 명령의 출력 예입니다.

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Tue, 07 Sep 2021 20:59:21 GMT
Server: Kestrel
Transfer-Encoding: chunked

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

이번에는 반환된 JSON이 단일 항목의 배열입니다.

이 앱은 메모리 내 데이터베이스를 사용합니다. 앱이 중지된 후 시작되면 이전 GET 요청이 데이터를 반환하지 않습니다. 데이터가 반환되지 않으면 앱에 데이터를 POST합니다.

라우팅 및 URL 경로

[HttpGet] 특성은 HTTP GET 요청에 응답하는 메서드를 나타냅니다. 각 방법에 대한 URL 경로는 다음과 같이 구성됩니다.

  • 컨트롤러의 Route 특성에서 템플릿 문자열로 시작합니다.

    [Route("api/[controller]")]
    [ApiController]
    public class TodoItemsController : ControllerBase
    
  • [controller]를 컨트롤러의 이름으로 바꿉니다. 일반적으로 컨트롤러 클래스 이름에서 "Controller" 접미사를 뺀 이름입니다. 이 샘플의 경우 컨트롤러 클래스 이름은 TodoItemsController이므로 컨트롤러 이름은 “TodoItems”입니다. ASP.NET Core 라우팅은 대/소문자를 구분하지 않습니다.

  • [HttpGet] 특성에 경로 템플릿(예: [HttpGet("products")])이 있는 경우 경로에 추가합니다. 이 샘플은 템플릿을 사용하지 않습니다. 자세한 내용은 Http[동사] 특성을 사용한 특성 라우팅을 참조하세요.

다음 GetTodoItem 메서드에서 "{id}"는 할 일 항목의 고유 식별자에 대한 자리 표시자 변수입니다. GetTodoItem이 호출되면 URL의 "{id}" 값을 id 매개 변수의 메서드에 제공합니다.

[HttpGet("{id}")]
public async Task<ActionResult<TodoItem>> GetTodoItem(long id)
{
    var todoItem = await _context.TodoItems.FindAsync(id);

    if (todoItem == null)
    {
        return NotFound();
    }

    return todoItem;
}

반환 값

GetTodoItemsGetTodoItem 메서드의 반환 형식은 ActionResult<T> 형식입니다. ASP.NET Core는 자동으로 JSON에 개체를 직렬화하고 JSON을 응답 메시지의 본문에 기록합니다. 이 반환 형식의 응답 코드는 200 OK이며 처리되지 않은 예외가 없다고 가정합니다. 처리되지 않은 예외는 5xx 오류로 변환됩니다.

ActionResult 반환 형식은 다양한 HTTP 상태 코드를 나타낼 수 있습니다. 예를 들어 GetTodoItem은 두 가지 상태 값을 반환할 수 있습니다.

  • 요청된 ID와 일치하는 항목이 없는 경우 메서드에서 404 상태NotFound 오류 코드를 반환합니다.
  • 그렇지 않으면 메서드가 JSON 응답 본문에서 200을 반환합니다. item을 반환하면 HTTP 200 응답이 발생합니다.

PutTodoItem 메서드

다음과 같이 PutTodoItem 메서드를 검사합니다.

[HttpPut("{id}")]
public async Task<IActionResult> PutTodoItem(long id, TodoItem todoItem)
{
    if (id != todoItem.Id)
    {
        return BadRequest();
    }

    _context.Entry(todoItem).State = EntityState.Modified;

    try
    {
        await _context.SaveChangesAsync();
    }
    catch (DbUpdateConcurrencyException)
    {
        if (!TodoItemExists(id))
        {
            return NotFound();
        }
        else
        {
            throw;
        }
    }

    return NoContent();
}

HTTP PUT을 사용하는 것을 제외하고 PutTodoItemPostTodoItem와 비슷합니다. 응답은 204(콘텐츠 없음)입니다. HTTP 사양에 따라 PUT 요청의 경우 클라이언트는 변경 내용만이 아니라 전체 업데이트된 엔터티를 보내야 합니다. 부분 업데이트를 지원하려면 HTTP PATCH를 사용합니다.

다음 섹션에서 PutTodoItem을 호출하는 중 오류가 발생하면 GET을 호출하여 데이터베이스에 항목이 있는지 확인합니다.

PutTodoItem 메서드 테스트

이 샘플은 앱이 시작될 때마다 초기화되어야 하는 메모리 내 데이터베이스를 사용합니다. PUT 호출을 실행하기 전에 데이터베이스에 항목이 있어야 합니다. GET을 호출하여 PUT 호출을 실행하기 전에 데이터베이스에 항목이 있는지 확인합니다.

Id = 1인 할 일 항목을 업데이트하고 해당 이름을 "feed fish"로 설정합니다.

connect https://localhost:5001/api/todoitems/1
put -h Content-Type=application/json -c "{"id":1,"name":"feed fish","isComplete":true}"

다음은 명령의 출력 예입니다.

HTTP/1.1 204 No Content
Date: Tue, 07 Sep 2021 21:20:47 GMT
Server: Kestrel

DeleteTodoItem 메서드

다음과 같이 DeleteTodoItem 메서드를 검사합니다.

[HttpDelete("{id}")]
public async Task<IActionResult> DeleteTodoItem(long id)
{
    var todoItem = await _context.TodoItems.FindAsync(id);
    if (todoItem == null)
    {
        return NotFound();
    }

    _context.TodoItems.Remove(todoItem);
    await _context.SaveChangesAsync();

    return NoContent();
}

DeleteTodoItem 메서드 테스트

Id = 1인 할 일 항목을 삭제합니다.

connect https://localhost:5001/api/todoitems/1
delete

다음은 명령의 출력 예입니다.

HTTP/1.1 204 No Content
Date: Tue, 07 Sep 2021 21:43:00 GMT
Server: Kestrel

과도한 게시 방지

현재 샘플 앱은 전체 TodoItem 개체를 공개합니다. 일반적으로 프로덕션 앱은 모델의 하위 집합을 사용하여 입력 및 반환되는 데이터를 제한합니다. 이 동작에는 여러 가지 이유가 있으며, 대표적인 이유는 보안입니다. 일반적으로 모델의 하위 집합을 DTO(데이터 전송 개체), 입력 모델 또는 뷰 모델이라고 합니다. 이 자습서에서는 DTO가 사용되었습니다.

DTO는 다음과 같은 용도로 사용할 수 있습니다.

  • 과도한 게시를 방지합니다.
  • 클라이언트에 표시되지 않아야 하는 속성을 숨깁니다.
  • 페이로드 크기를 줄이기 위해 일부 속성을 생략합니다.
  • 중첩된 개체를 포함하는 개체 그래프를 평면화합니다. 클라이언트에는 평면화된 개체 그래프가 더 편리할 수 있습니다.

DTO 방법을 설명하려면 비밀 필드를 포함하도록 TodoItem 클래스를 업데이트합니다.

namespace TodoApi.Models
{
    public class TodoItem
    {
        public long Id { get; set; }
        public string? Name { get; set; }
        public bool IsComplete { get; set; }
        public string? Secret { get; set; }
    }
}

이 앱에서는 숨겨진 필드를 숨겨야 하지만, 관리 앱은 숨겨진 필드를 공개할 수 있습니다.

비밀 필드를 게시하고 가져올 수 있는지 확인합니다.

DTO 모델을 만듭니다.

namespace TodoApi.Models
{
    public class TodoItemDTO
    {
        public long Id { get; set; }
        public string? Name { get; set; }
        public bool IsComplete { get; set; }
    }
}

TodoItemDTO를 사용하도록 TodoItemsController를 업데이트합니다.

using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using TodoApi.Models;

namespace TodoApi.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class TodoItemsController : ControllerBase
    {
        private readonly TodoContext _context;

        public TodoItemsController(TodoContext context)
        {
            _context = context;
        }

        // GET: api/TodoItems
        [HttpGet]
        public async Task<ActionResult<IEnumerable<TodoItemDTO>>> GetTodoItems()
        {
            return await _context.TodoItems
                .Select(x => ItemToDTO(x))
                .ToListAsync();
        }

        // GET: api/TodoItems/5
        [HttpGet("{id}")]
        public async Task<ActionResult<TodoItemDTO>> GetTodoItem(long id)
        {
            var todoItem = await _context.TodoItems.FindAsync(id);

            if (todoItem == null)
            {
                return NotFound();
            }

            return ItemToDTO(todoItem);
        }
        // PUT: api/TodoItems/5
        // To protect from overposting attacks, see https://go.microsoft.com/fwlink/?linkid=2123754
        [HttpPut("{id}")]
        public async Task<IActionResult> UpdateTodoItem(long id, TodoItemDTO todoItemDTO)
        {
            if (id != todoItemDTO.Id)
            {
                return BadRequest();
            }

            var todoItem = await _context.TodoItems.FindAsync(id);
            if (todoItem == null)
            {
                return NotFound();
            }

            todoItem.Name = todoItemDTO.Name;
            todoItem.IsComplete = todoItemDTO.IsComplete;

            try
            {
                await _context.SaveChangesAsync();
            }
            catch (DbUpdateConcurrencyException) when (!TodoItemExists(id))
            {
                return NotFound();
            }

            return NoContent();
        }
        // POST: api/TodoItems
        // To protect from overposting attacks, see https://go.microsoft.com/fwlink/?linkid=2123754
        [HttpPost]
        public async Task<ActionResult<TodoItemDTO>> CreateTodoItem(TodoItemDTO todoItemDTO)
        {
            var todoItem = new TodoItem
            {
                IsComplete = todoItemDTO.IsComplete,
                Name = todoItemDTO.Name
            };

            _context.TodoItems.Add(todoItem);
            await _context.SaveChangesAsync();

            return CreatedAtAction(
                nameof(GetTodoItem),
                new { id = todoItem.Id },
                ItemToDTO(todoItem));
        }

        // DELETE: api/TodoItems/5
        [HttpDelete("{id}")]
        public async Task<IActionResult> DeleteTodoItem(long id)
        {
            var todoItem = await _context.TodoItems.FindAsync(id);

            if (todoItem == null)
            {
                return NotFound();
            }

            _context.TodoItems.Remove(todoItem);
            await _context.SaveChangesAsync();

            return NoContent();
        }

        private bool TodoItemExists(long id)
        {
            return _context.TodoItems.Any(e => e.Id == id);
        }

        private static TodoItemDTO ItemToDTO(TodoItem todoItem) =>
            new TodoItemDTO
            {
                Id = todoItem.Id,
                Name = todoItem.Name,
                IsComplete = todoItem.IsComplete
            };
    }
}

비밀 필드를 게시하거나 가져올 수 없음을 확인합니다.

JavaScript를 사용하여 웹 API 호출

자습서: JavaScript로 ASP.NET Core 웹 API 호출하기를 참조하세요.

이 자습서에서는 다음과 같은 작업을 수행하는 방법을 살펴봅니다.

  • 웹 API 프로젝트를 만듭니다.
  • 모델 클래스와 데이터베이스 컨텍스트를 추가합니다.
  • CRUD 메서드로 컨트롤러를 스캐폴드합니다.
  • 라우팅, URL 경로 및 반환 값을 구성합니다.
  • Postman을 사용하여 웹 API를 호출합니다.

과정을 마치면 웹 API를 통해 데이터베이스에 저장된 “할 일” 항목을 관리할 수 있습니다.

개요

이 자습서에서는 다음 API를 만듭니다.

API 설명 요청 본문 응답 본문
GET /api/todoitems 할 일 항목 모두 가져오기 없음 할 일 항목의 배열
GET /api/todoitems/{id} ID로 항목 가져오기 없음 할 일 항목
POST /api/todoitems 새 항목 추가 할 일 항목 할 일 항목
PUT /api/todoitems/{id} 기존 항목 업데이트 할 일 항목 없음
DELETE /api/todoitems/{id}     항목 삭제 없음 없음

다음 다이어그램에서는 앱의 디자인을 보여줍니다.

The client is represented by a box on the left. It submits a request and receives a response from the application, a box drawn on the right. Within the application box, three boxes represent the controller, the model, and the data access layer. The request comes into the application's controller, and read/write operations occur between the controller and the data access layer. The model is serialized and returned to the client in the response.

사전 요구 사항

웹 프로젝트 만들기

  • 파일 메뉴에서 새로 만들기>프로젝트를 선택합니다.
  • ASP.NET Core Web API 템플릿을 선택하고 다음을 클릭합니다.
  • 프로젝트 이름을 TodoApi로 지정하고 만들기를 클릭합니다.
  • 새 ASP.NET Core 웹 애플리케이션 만들기 대화 상자에서 .NET CoreASP.NET Core 5.0이 선택되었는지 확인합니다. API 템플릿을 선택하고 만들기를 클릭합니다.

VS new project dialog

참고

.NET 앱에 패키지를 추가하는 지침은 패키지 사용 워크플로(NuGet 설명서)에서 패키지 설치 및 관리 아래의 문서를 참조하세요. NuGet.org에서 올바른 패키지 버전을 확인합니다.

프로젝트 테스트

프로젝트 템플릿은 Swagger를 지원하는 WeatherForecast API를 만듭니다.

Ctrl+F5를 눌러 디버거 없이 실행합니다.

프로젝트가 SSL을 사용하도록 아직 구성되지 않은 경우 Visual Studio에 다음 대화 상자가 표시됩니다.

This project is configured to use SSL. To avoid SSL warnings in the browser you can choose to trust the self-signed certificate that IIS Express has generated. Would you like to trust the IIS Express SSL certificate?

IIS Express SSL 인증서를 신뢰하는 경우 를 선택합니다.

다음 대화 상자가 표시됩니다.

Security warning dialog

개발 인증서를 신뢰하는 데 동의하는 경우 를 선택합니다.

Firefox 브라우저를 신뢰하는 방법에 대한 자세한 내용은 Firefox SEC_ERROR_INADEQUATE_KEY_USAGE 인증서 오류를 참조하세요.

Visual Studio에서 다음을 시작합니다.

  • IIS Express 웹 서버.
  • 기본 브라우저를 시작하고 https://localhost:<port>/swagger/index.html로 이동합니다. 여기서 <port>는 임의로 선택한 포트 번호입니다.

Swagger 페이지 /swagger/index.html이 표시됩니다. GET>사용해 보기>실행을 선택합니다. 페이지에 다음이 표시됩니다.

  • WeatherForecast API를 테스트할 Curl 명령
  • WeatherForecast API를 테스트할 URL
  • 응답 코드, 본문 및 헤더
  • 미디어 유형과 예제 값 및 스키마가 포함된 드롭다운 목록 상자

Swagger 페이지가 표시되지 않는 경우 이 GitHub 이슈를 참조하세요.

Swagger는 웹 API에 유용한 설명서 및 도움말 페이지를 생성하는 데 사용됩니다. 이 자습서에서는 웹 API 만들기에 대해 집중적으로 다룹니다. Swagger에 대한 자세한 내용은 Swagger/OpenAPI를 사용한 ASP.NET Core Web API 설명서를 참조하세요.

브라우저에서 요청 URL을 복사하여 붙여넣습니다. https://localhost:<port>/weatherforecast

다음과 비슷한 JSON이 반환됩니다.

[
    {
        "date": "2019-07-16T19:04:05.7257911-06:00",
        "temperatureC": 52,
        "temperatureF": 125,
        "summary": "Mild"
    },
    {
        "date": "2019-07-17T19:04:05.7258461-06:00",
        "temperatureC": 36,
        "temperatureF": 96,
        "summary": "Warm"
    },
    {
        "date": "2019-07-18T19:04:05.7258467-06:00",
        "temperatureC": 39,
        "temperatureF": 102,
        "summary": "Cool"
    },
    {
        "date": "2019-07-19T19:04:05.7258471-06:00",
        "temperatureC": 10,
        "temperatureF": 49,
        "summary": "Bracing"
    },
    {
        "date": "2019-07-20T19:04:05.7258474-06:00",
        "temperatureC": -1,
        "temperatureF": 31,
        "summary": "Chilly"
    }
]

launchUrl 업데이트

Properties\launchSettings.json에서 launchUrl"swagger"에서 "api/todoitems"로 업데이트합니다.

"launchUrl": "api/todoitems",

Swagger가 제거되므로 위 태그는 다음 섹션에 추가된 컨트롤러의 GET 메서드로 시작되는 URL을 변경합니다.

모델 클래스 추가

모델은 앱에서 관리하는 데이터를 나타내는 일련의 클래스입니다. 이 앱에 대한 모델은 단일 TodoItem 클래스입니다.

  • 솔루션 탐색기에서 프로젝트를 마우스 오른쪽 단추로 클릭합니다. 추가>새 폴더를 선택합니다. 폴더 이름을 Models서 무료 평가판 계정을 등록할 수 있습니다.

  • Models 폴더를 마우스 오른쪽 단추로 클릭하고 추가>클래스를 선택합니다. 클래스 이름을 TodoItem으로 지정하고 추가를 선택합니다.

  • 템플릿 코드를 다음으로 바꿉니다.

namespace TodoApi.Models
{
    public class TodoItem
    {
        public long Id { get; set; }
        public string Name { get; set; }
        public bool IsComplete { get; set; }
    }
}

Id 속성은 관계형 데이터베이스에서 고유 키로 작동합니다.

모델 클래스는 프로젝트의 어디로든 이동할 수 있지만 규칙에 따라 Models 폴더를 사용합니다.

데이터베이스 컨텍스트 추가

데이터베이스 컨텍스트는 데이터 모델에 맞게 Entity Framework 기능을 조정하는 주 클래스입니다. Microsoft.EntityFrameworkCore.DbContext 클래스에서 파생시키는 방식으로 이 클래스를 만듭니다.

NuGet 패키지 추가

  • 도구 메뉴에서 NuGet 패키지 관리자 > 솔루션용 NuGet 패키지 관리를 선택합니다.
  • 찾아보기 탭을 선택한 다음, 검색 상자에 Microsoft.EntityFrameworkCore.InMemory를 입력합니다.
  • 왼쪽 창에서 Microsoft.EntityFrameworkCore.InMemory를 선택합니다.
  • 오른쪽 창에서 프로젝트 확인란을 선택하고 설치를 선택합니다.

NuGet Package Manager

TodoContext 데이터베이스 컨텍스트 추가

  • Models 폴더를 마우스 오른쪽 단추로 클릭하고 추가>클래스를 선택합니다. 클래스 이름을 TodoContext로 지정하고 추가를 클릭합니다.
  • 다음 코드를 입력합니다.

    using Microsoft.EntityFrameworkCore;
    
    namespace TodoApi.Models
    {
        public class TodoContext : DbContext
        {
            public TodoContext(DbContextOptions<TodoContext> options)
                : base(options)
            {
            }
    
            public DbSet<TodoItem> TodoItems { get; set; }
        }
    }
    

데이터베이스 컨텍스트 등록

ASP.NET Core에서는 DB 컨텍스트와 같은 서비스를 DI(종속성 주입) 컨테이너에 등록해야 합니다. 컨테이너는 컨트롤러에 서비스를 제공합니다.

다음 코드를 사용하여 Startup.cs을 업데이트합니다.

// Unused usings removed
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.EntityFrameworkCore;
using TodoApi.Models;

namespace TodoApi
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers();

            services.AddDbContext<TodoContext>(opt =>
                                               opt.UseInMemoryDatabase("TodoList"));
            //services.AddSwaggerGen(c =>
            //{
            //    c.SwaggerDoc("v1", new OpenApiInfo { Title = "TodoApi", Version = "v1" });
            //});
        }

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                //app.UseSwagger();
                //app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "TodoApi v1"));
            }

            app.UseHttpsRedirection();
            app.UseRouting();

            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }
    }
}

위의 코드는

  • Swagger 호출을 제거합니다.
  • 사용되지 않는 using 선언을 제거합니다.
  • DI 컨테이너에 데이터베이스 컨텍스트를 추가합니다.
  • 데이터베이스 컨텍스트가 메모리 내 데이터베이스를 사용하도록 지정합니다.

컨트롤러 스캐폴드

  • Controllers 폴더를 마우스 오른쪽 단추로 클릭합니다.

  • 추가>스캐폴드 항목 새로 만들기를 선택합니다.

  • Entity Framework를 사용하며 동작이 포함된 API 컨트롤러를 선택하고 추가를 선택합니다.

  • Entity Framework를 사용하며 동작이 포함된 API 컨트롤러 추가 대화 상자에서:

    • 모델 클래스에서 TodoItem (TodoApi.Models) 을 선택합니다.
    • 데이터 컨텍스트 클래스에서 TodoContext (TodoApi.Models) 를 선택합니다.
    • 추가를 선택합니다.

생성된 코드는:

  • [ApiController] 특성으로 클래스를 표시합니다. 이 특성은 컨트롤러가 웹 API 요청에 응답함을 나타냅니다. 이 특성이 사용하도록 설정하는 특정 동작에 대한 자세한 내용은 ASP.NET Core로 Web API 만들기를 참조하세요.
  • DI를 사용하여 데이터베이스 컨텍스트(TodoContext)를 컨트롤러에 삽입합니다. 컨트롤러의 각 CRUD 메서드에서 해당 데이터베이스 컨텍스트를 사용합니다.

ASP.NET Core 템플릿과 관련해서 다음 사항을 확인합니다.

  • 뷰가 있는 컨트롤러용 ASP.NET Core 템플릿의 경우, 경로 템플릿에 [action]이 포함됩니다.
  • API 컨트롤러용 ASP.NET Core 템플릿의 경우, 경로 템플릿에 [action]이 포함되지 않습니다.

[action] 토큰이 경로 템플릿에 없는 경우 경로에서 작업 이름이 제외됩니다. 즉, 일치하는 경로에서 작업과 연결된 메서드 이름이 사용되지 않습니다.

PostTodoItem 만들기 메서드 업데이트

nameof 연산자를 사용하도록 PostTodoItem의 return 문을 업데이트합니다.

// POST: api/TodoItems
[HttpPost]
public async Task<ActionResult<TodoItem>> PostTodoItem(TodoItem todoItem)
{
    _context.TodoItems.Add(todoItem);
    await _context.SaveChangesAsync();

    //return CreatedAtAction("GetTodoItem", new { id = todoItem.Id }, todoItem);
    return CreatedAtAction(nameof(GetTodoItem), new { id = todoItem.Id }, todoItem);
}

위의 코드는 [HttpPost] 특성으로 표시되는 HTTP POST 메서드입니다. 이 메서드는 HTTP 요청 본문에서 할 일 항목 값을 가져옵니다.

자세한 내용은 Http[동사] 특성을 사용한 특성 라우팅을 참조하세요.

CreatedAtAction 메서드는 다음 작업을 수행합니다.

  • 성공하면 HTTP 201 상태 코드를 반환합니다. HTTP 201은 서버에서 새 리소스를 만드는 HTTP POST 메서드의 표준 응답입니다.
  • 응답에 대한 위치 헤더를 추가합니다. Location 헤더는 새로 만들어진 할 일 항목의 URI를 지정합니다. 자세한 내용은 10.2.2 201 생성됨을 참조하세요.
  • Location 헤더의 URI를 만들려면 GetTodoItem 작업을 참조합니다. C# nameof 키워드는 CreatedAtAction 호출에서 작업 이름의 하드 코딩을 방지하는 데 사용됩니다.

Postman을 설치합니다.

이 자습서에서는 Postman을 사용하여 웹 API를 테스트합니다.

  • Postman을 설치합니다.
  • 웹앱을 시작합니다.
  • Postman을 시작합니다.
  • SSL 인증서 확인을 사용하지 않도록 설정합니다.
    • Windows용 Postman: 파일>설정(일반 탭)을 선택하고, SSL 인증서 확인을 사용하지 않도록 설정합니다.
    • macOS용 Postman: Postman>설정(일반 탭)을 선택하고, SSL 인증서 확인을 사용하지 않도록 설정합니다.

      경고

      컨트롤러를 테스트한 후에 SSL 인증서 확인을 다시 사용하도록 설정합니다.

Postman을 사용하여 PostTodoItem 테스트

  • 새 요청을 만듭니다.

  • HTTP 메서드를 POST로 설정합니다.

  • URI를 https://localhost:<port>/api/todoitems로 설정합니다. 예: https://localhost:5001/api/todoitems.

  • 본문 탭을 선택합니다.

  • 원시 라디오 단추를 선택합니다.

  • 유형을 JSON(application/json)으로 설정합니다.

  • 요청 본문에서 할 일 항목에 대한 JSON을 입력합니다.

    {
      "name":"walk dog",
      "isComplete":true
    }
    
  • 보내기를 선택합니다.

    Postman with create request

위치 헤더 URI 테스트

위치 헤더 URI는 브라우저에서 테스트할 수 있습니다. 위치 헤더 URI를 복사하여 브라우저에 붙여넣습니다.

Postman에서 테스트하려면 다음을 수행합니다.

  • 응답 창에서 헤더 탭을 선택합니다.

  • 위치 헤더 값을 복사합니다.

    Headers tab of the Postman console

  • HTTP 메서드를 GET로 설정합니다.

  • URI를 https://localhost:<port>/api/todoitems/1로 설정합니다. 예: https://localhost:5001/api/todoitems/1.

  • 보내기를 선택합니다.

GET 메서드 검사

두 개의 GET 엔드포인트가 구현됩니다.

  • GET /api/todoitems
  • GET /api/todoitems/{id}

브라우저 또는 Postman에서 두 개의 엔드포인트를 호출하여 앱을 테스트합니다. 예를 들어:

  • https://localhost:5001/api/todoitems
  • https://localhost:5001/api/todoitems/1

GetTodoItems를 호출하면 다음과 비슷한 응답이 생성됩니다.

[
  {
    "id": 1,
    "name": "Item1",
    "isComplete": false
  }
]

Postman을 사용하여 Get 테스트

  • 새 요청을 만듭니다.
  • HTTP 메서드를 GET으로 설정합니다.
  • 요청 URI를 https://localhost:<port>/api/todoitems로 설정합니다. 예: https://localhost:5001/api/todoitems.
  • Postman에서 두 개의 창 보기를 설정합니다.
  • 보내기를 선택합니다.

이 앱은 메모리 내 데이터베이스를 사용합니다. 앱이 중지된 후 시작되면 이전 GET 요청이 데이터를 반환하지 않습니다. 데이터가 반환되지 않으면 앱에 데이터를 POST합니다.

라우팅 및 URL 경로

[HttpGet] 특성은 HTTP GET 요청에 응답하는 메서드를 나타냅니다. 각 방법에 대한 URL 경로는 다음과 같이 구성됩니다.

  • 컨트롤러의 Route 특성에서 템플릿 문자열로 시작합니다.

    [Route("api/[controller]")]
    [ApiController]
    public class TodoItemsController : ControllerBase
    {
        private readonly TodoContext _context;
    
        public TodoItemsController(TodoContext context)
        {
            _context = context;
        }
    
  • [controller]를 컨트롤러의 이름으로 바꿉니다. 일반적으로 컨트롤러 클래스 이름에서 "Controller" 접미사를 뺀 이름입니다. 이 샘플의 경우 컨트롤러 클래스 이름은 TodoItemsController이므로 컨트롤러 이름은 “TodoItems”입니다. ASP.NET Core 라우팅은 대/소문자를 구분하지 않습니다.

  • [HttpGet] 특성에 경로 템플릿(예: [HttpGet("products")])이 있는 경우 경로에 추가합니다. 이 샘플은 템플릿을 사용하지 않습니다. 자세한 내용은 Http[동사] 특성을 사용한 특성 라우팅을 참조하세요.

다음 GetTodoItem 메서드에서 "{id}"는 할 일 항목의 고유 식별자에 대한 자리 표시자 변수입니다. GetTodoItem이 호출되면 URL의 "{id}" 값을 id 매개 변수의 메서드에 제공합니다.

// GET: api/TodoItems/5
[HttpGet("{id}")]
public async Task<ActionResult<TodoItem>> GetTodoItem(long id)
{
    var todoItem = await _context.TodoItems.FindAsync(id);

    if (todoItem == null)
    {
        return NotFound();
    }

    return todoItem;
}

반환 값

GetTodoItemsGetTodoItem 메서드의 반환 형식은 ActionResult<T> 형식입니다. ASP.NET Core는 자동으로 JSON에 개체를 직렬화하고 JSON을 응답 메시지의 본문에 기록합니다. 이 반환 형식의 응답 코드는 200 OK이며 처리되지 않은 예외가 없다고 가정합니다. 처리되지 않은 예외는 5xx 오류로 변환됩니다.

ActionResult 반환 형식은 다양한 HTTP 상태 코드를 나타낼 수 있습니다. 예를 들어 GetTodoItem은 두 가지 상태 값을 반환할 수 있습니다.

  • 요청된 ID와 일치하는 항목이 없는 경우 메서드에서 404 상태NotFound 오류 코드를 반환합니다.
  • 그렇지 않으면 메서드가 JSON 응답 본문에서 200을 반환합니다. item을 반환하면 HTTP 200 응답이 발생합니다.

PutTodoItem 메서드

다음과 같이 PutTodoItem 메서드를 검사합니다.

// PUT: api/TodoItems/5
[HttpPut("{id}")]
public async Task<IActionResult> PutTodoItem(long id, TodoItem todoItem)
{
    if (id != todoItem.Id)
    {
        return BadRequest();
    }

    _context.Entry(todoItem).State = EntityState.Modified;

    try
    {
        await _context.SaveChangesAsync();
    }
    catch (DbUpdateConcurrencyException)
    {
        if (!TodoItemExists(id))
        {
            return NotFound();
        }
        else
        {
            throw;
        }
    }

    return NoContent();
}

HTTP PUT을 사용하는 것을 제외하고 PutTodoItemPostTodoItem와 비슷합니다. 응답은 204(콘텐츠 없음)입니다. HTTP 사양에 따라 PUT 요청의 경우 클라이언트는 변경 내용만이 아니라 전체 업데이트된 엔터티를 보내야 합니다. 부분 업데이트를 지원하려면 HTTP PATCH를 사용합니다.

PutTodoItem을 호출하는 중 오류가 발생하면 GET을 호출하여 데이터베이스에 항목이 있는지 확인합니다.

PutTodoItem 메서드 테스트

이 샘플은 앱이 시작될 때마다 초기화되어야 하는 메모리 내 데이터베이스를 사용합니다. PUT 호출을 실행하기 전에 데이터베이스에 항목이 있어야 합니다. GET을 호출하여 PUT 호출을 실행하기 전에 데이터베이스에 항목이 있는지 확인합니다.

Id = 1인 할 일 항목을 업데이트하고 해당 이름을 "feed fish"로 설정합니다.

  {
    "Id":1,
    "name":"feed fish",
    "isComplete":true
  }

다음 이미지는 Postman 업데이트를 보여줍니다.

Postman console showing 204 (No Content) response

DeleteTodoItem 메서드

다음과 같이 DeleteTodoItem 메서드를 검사합니다.

// DELETE: api/TodoItems/5
[HttpDelete("{id}")]
public async Task<IActionResult> DeleteTodoItem(long id)
{
    var todoItem = await _context.TodoItems.FindAsync(id);
    if (todoItem == null)
    {
        return NotFound();
    }

    _context.TodoItems.Remove(todoItem);
    await _context.SaveChangesAsync();

    return NoContent();
}

DeleteTodoItem 메서드 테스트

Postman을 사용하여 할 일 항목을 삭제합니다.

  • 메서드를 DELETE로 설정합니다.
  • 예를 들어 삭제할 개체의 URI를 https://localhost:5001/api/todoitems/1로 설정합니다.
  • 보내기를 선택합니다.

과도한 게시 방지

현재 샘플 앱은 전체 TodoItem 개체를 공개합니다. 일반적으로 프로덕션 앱은 모델의 하위 집합을 사용하여 입력 및 반환되는 데이터를 제한합니다. 이 동작에는 여러 가지 이유가 있으며, 보안이 주요 이유 중 하나입니다. 일반적으로 모델의 하위 집합을 DTO(데이터 전송 개체), 입력 모델 또는 뷰 모델이라고 합니다. 이 문서에서는 DTO를 사용합니다.

DTO는 다음과 같은 용도로 사용할 수 있습니다.

  • 과도한 게시를 방지합니다.
  • 클라이언트에 표시되지 않아야 하는 속성을 숨깁니다.
  • 페이로드 크기를 줄이기 위해 일부 속성을 생략합니다.
  • 중첩된 개체를 포함하는 개체 그래프를 평면화합니다. 클라이언트에는 평면화된 개체 그래프가 더 편리할 수 있습니다.

DTO 방법을 설명하려면 비밀 필드를 포함하도록 TodoItem 클래스를 업데이트합니다.

namespace TodoApi.Models
{
    public class TodoItem
    {
        public long Id { get; set; }
        public string Name { get; set; }
        public bool IsComplete { get; set; }
        public string Secret { get; set; }
    }
}

이 앱에서는 숨겨진 필드를 숨겨야 하지만, 관리 앱은 숨겨진 필드를 공개할 수 있습니다.

비밀 필드를 게시하고 가져올 수 있는지 확인합니다.

DTO 모델을 만듭니다.

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

TodoItemDTO를 사용하도록 TodoItemsController를 업데이트합니다.

// GET: api/TodoItems
[HttpGet]
public async Task<ActionResult<IEnumerable<TodoItemDTO>>> GetTodoItems()
{
    return await _context.TodoItems
        .Select(x => ItemToDTO(x))
        .ToListAsync();
}

[HttpGet("{id}")]
public async Task<ActionResult<TodoItemDTO>> GetTodoItem(long id)
{
    var todoItem = await _context.TodoItems.FindAsync(id);

    if (todoItem == null)
    {
        return NotFound();
    }

    return ItemToDTO(todoItem);
}

[HttpPut("{id}")]
public async Task<IActionResult> UpdateTodoItem(long id, TodoItemDTO todoItemDTO)
{
    if (id != todoItemDTO.Id)
    {
        return BadRequest();
    }

    var todoItem = await _context.TodoItems.FindAsync(id);
    if (todoItem == null)
    {
        return NotFound();
    }

    todoItem.Name = todoItemDTO.Name;
    todoItem.IsComplete = todoItemDTO.IsComplete;

    try
    {
        await _context.SaveChangesAsync();
    }
    catch (DbUpdateConcurrencyException) when (!TodoItemExists(id))
    {
        return NotFound();
    }

    return NoContent();
}

[HttpPost]
public async Task<ActionResult<TodoItemDTO>> CreateTodoItem(TodoItemDTO todoItemDTO)
{
    var todoItem = new TodoItem
    {
        IsComplete = todoItemDTO.IsComplete,
        Name = todoItemDTO.Name
    };

    _context.TodoItems.Add(todoItem);
    await _context.SaveChangesAsync();

    return CreatedAtAction(
        nameof(GetTodoItem),
        new { id = todoItem.Id },
        ItemToDTO(todoItem));
}

[HttpDelete("{id}")]
public async Task<IActionResult> DeleteTodoItem(long id)
{
    var todoItem = await _context.TodoItems.FindAsync(id);

    if (todoItem == null)
    {
        return NotFound();
    }

    _context.TodoItems.Remove(todoItem);
    await _context.SaveChangesAsync();

    return NoContent();
}

private bool TodoItemExists(long id) =>
     _context.TodoItems.Any(e => e.Id == id);

private static TodoItemDTO ItemToDTO(TodoItem todoItem) =>
    new TodoItemDTO
    {
        Id = todoItem.Id,
        Name = todoItem.Name,
        IsComplete = todoItem.IsComplete
    };

비밀 필드를 게시하거나 가져올 수 없음을 확인합니다.

JavaScript를 사용하여 웹 API 호출

자습서: JavaScript로 ASP.NET Core 웹 API 호출하기를 참조하세요.

이 자습서에서는 다음과 같은 작업을 수행하는 방법을 살펴봅니다.

  • 웹 API 프로젝트를 만듭니다.
  • 모델 클래스와 데이터베이스 컨텍스트를 추가합니다.
  • CRUD 메서드로 컨트롤러를 스캐폴드합니다.
  • 라우팅, URL 경로 및 반환 값을 구성합니다.
  • Postman을 사용하여 웹 API를 호출합니다.

과정을 마치면 웹 API를 통해 데이터베이스에 저장된 “할 일” 항목을 관리할 수 있습니다.

개요

이 자습서에서는 다음 API를 만듭니다.

API 설명 요청 본문 응답 본문
GET /api/todoitems 할 일 항목 모두 가져오기 없음 할 일 항목의 배열
GET /api/todoitems/{id} ID로 항목 가져오기 없음 할 일 항목
POST /api/todoitems 새 항목 추가 할 일 항목 할 일 항목
PUT /api/todoitems/{id} 기존 항목 업데이트 할 일 항목 없음
DELETE /api/todoitems/{id}     항목 삭제 없음 없음

다음 다이어그램에서는 앱의 디자인을 보여줍니다.

The client is represented by a box on the left. It submits a request and receives a response from the application, a box drawn on the right. Within the application box, three boxes represent the controller, the model, and the data access layer. The request comes into the application's controller, and read/write operations occur between the controller and the data access layer. The model is serialized and returned to the client in the response.

사전 요구 사항

웹 프로젝트 만들기

  • 파일 메뉴에서 새로 만들기>프로젝트를 선택합니다.
  • ASP.NET Core 웹 애플리케이션 템플릿을 선택하고 다음을 클릭합니다.
  • 프로젝트 이름을 TodoApi로 지정하고 만들기를 클릭합니다.
  • 새 ASP.NET Core 웹 애플리케이션 만들기 대화 상자에서 .NET CoreASP.NET Core 3.1이 선택되었는지 확인합니다. API 템플릿을 선택하고 만들기를 클릭합니다.

VS new project dialog

참고

.NET 앱에 패키지를 추가하는 지침은 패키지 사용 워크플로(NuGet 설명서)에서 패키지 설치 및 관리 아래의 문서를 참조하세요. NuGet.org에서 올바른 패키지 버전을 확인합니다.

API 테스트

프로젝트 템플릿은 WeatherForecast API를 만듭니다. 브라우저에서 Get 메서드를 호출하여 앱을 테스트합니다.

Ctrl+F5 키를 눌러 앱을 실행합니다. Visual Studio가 브라우저를 시작하고 https://localhost:<port>/weatherforecast로 이동합니다. 여기서 <port>는 임의로 선택된 포트 번호입니다.

IIS Express 인증서를 신뢰해야 하는지 묻는 대화 상자가 표시되면 를 선택합니다. 다음으로, 보안 경고 대화 상자가 나타나면 를 선택합니다.

다음과 비슷한 JSON이 반환됩니다.

[
    {
        "date": "2019-07-16T19:04:05.7257911-06:00",
        "temperatureC": 52,
        "temperatureF": 125,
        "summary": "Mild"
    },
    {
        "date": "2019-07-17T19:04:05.7258461-06:00",
        "temperatureC": 36,
        "temperatureF": 96,
        "summary": "Warm"
    },
    {
        "date": "2019-07-18T19:04:05.7258467-06:00",
        "temperatureC": 39,
        "temperatureF": 102,
        "summary": "Cool"
    },
    {
        "date": "2019-07-19T19:04:05.7258471-06:00",
        "temperatureC": 10,
        "temperatureF": 49,
        "summary": "Bracing"
    },
    {
        "date": "2019-07-20T19:04:05.7258474-06:00",
        "temperatureC": -1,
        "temperatureF": 31,
        "summary": "Chilly"
    }
]

모델 클래스 추가

모델은 앱에서 관리하는 데이터를 나타내는 일련의 클래스입니다. 이 앱에 대한 모델은 단일 TodoItem 클래스입니다.

  • 솔루션 탐색기에서 프로젝트를 마우스 오른쪽 단추로 클릭합니다. 추가>새 폴더를 선택합니다. 폴더 이름을 Models서 무료 평가판 계정을 등록할 수 있습니다.

  • Models 폴더를 마우스 오른쪽 단추로 클릭하고 추가>클래스를 선택합니다. 클래스 이름을 TodoItem으로 지정하고 추가를 선택합니다.

  • 템플릿 코드를 다음 코드로 바꿉니다.

public class TodoItem
{
    public long Id { get; set; }
    public string Name { get; set; }
    public bool IsComplete { get; set; }
}

Id 속성은 관계형 데이터베이스에서 고유 키로 작동합니다.

모델 클래스는 프로젝트의 어디로든 이동할 수 있지만 규칙에 따라 Models 폴더를 사용합니다.

데이터베이스 컨텍스트 추가

데이터베이스 컨텍스트는 데이터 모델에 맞게 Entity Framework 기능을 조정하는 주 클래스입니다. Microsoft.EntityFrameworkCore.DbContext 클래스에서 파생시키는 방식으로 이 클래스를 만듭니다.

NuGet 패키지 추가

  • 도구 메뉴에서 NuGet 패키지 관리자 > 솔루션용 NuGet 패키지 관리를 선택합니다.
  • 찾아보기 탭을 선택한 다음, 검색 상자에 Microsoft.EntityFrameworkCore.InMemory를 입력합니다.
  • 왼쪽 창에서 Microsoft.EntityFrameworkCore.InMemory를 선택합니다.
  • 오른쪽 창에서 프로젝트 확인란을 선택하고 설치를 선택합니다.

NuGet Package Manager

TodoContext 데이터베이스 컨텍스트 추가

  • Models 폴더를 마우스 오른쪽 단추로 클릭하고 추가>클래스를 선택합니다. 클래스 이름을 TodoContext로 지정하고 추가를 클릭합니다.
  • 다음 코드를 입력합니다.

    using Microsoft.EntityFrameworkCore;
    
    namespace TodoApi.Models
    {
        public class TodoContext : DbContext
        {
            public TodoContext(DbContextOptions<TodoContext> options)
                : base(options)
            {
            }
    
            public DbSet<TodoItem> TodoItems { get; set; }
        }
    }
    

데이터베이스 컨텍스트 등록

ASP.NET Core에서는 DB 컨텍스트와 같은 서비스를 DI(종속성 주입) 컨테이너에 등록해야 합니다. 컨테이너는 컨트롤러에 서비스를 제공합니다.

Startup.cs를 다음 강조 표시된 코드로 업데이트합니다.

// Unused usings removed
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.EntityFrameworkCore;
using TodoApi.Models;

namespace TodoApi
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddDbContext<TodoContext>(opt =>
               opt.UseInMemoryDatabase("TodoList"));
            services.AddControllers();
        }

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseHttpsRedirection();

            app.UseRouting();

            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }
    }
}

위의 코드는

  • 사용되지 않는 using 선언을 제거합니다.
  • DI 컨테이너에 데이터베이스 컨텍스트를 추가합니다.
  • 데이터베이스 컨텍스트가 메모리 내 데이터베이스를 사용하도록 지정합니다.

컨트롤러 스캐폴드

  • Controllers 폴더를 마우스 오른쪽 단추로 클릭합니다.

  • 추가>스캐폴드 항목 새로 만들기를 선택합니다.

  • Entity Framework를 사용하며 동작이 포함된 API 컨트롤러를 선택하고 추가를 선택합니다.

  • Entity Framework를 사용하며 동작이 포함된 API 컨트롤러 추가 대화 상자에서:

    • 모델 클래스에서 TodoItem (TodoApi.Models) 을 선택합니다.
    • 데이터 컨텍스트 클래스에서 TodoContext (TodoApi.Models) 를 선택합니다.
    • 추가를 선택합니다.

생성된 코드는:

  • [ApiController] 특성으로 클래스를 표시합니다. 이 특성은 컨트롤러가 웹 API 요청에 응답함을 나타냅니다. 이 특성이 사용하도록 설정하는 특정 동작에 대한 자세한 내용은 ASP.NET Core로 Web API 만들기를 참조하세요.
  • DI를 사용하여 데이터베이스 컨텍스트(TodoContext)를 컨트롤러에 삽입합니다. 컨트롤러의 각 CRUD 메서드에서 해당 데이터베이스 컨텍스트를 사용합니다.

ASP.NET Core 템플릿과 관련해서 다음 사항을 확인합니다.

  • 뷰가 있는 컨트롤러용 ASP.NET Core 템플릿의 경우, 경로 템플릿에 [action]이 포함됩니다.
  • API 컨트롤러용 ASP.NET Core 템플릿의 경우, 경로 템플릿에 [action]이 포함되지 않습니다.

[action] 토큰이 경로 템플릿에 없는 경우 경로에서 작업 이름이 제외됩니다. 즉, 일치하는 경로에서 작업과 연결된 메서드 이름이 사용되지 않습니다.

PostTodoItem 만들기 메서드 검사

PostTodoItem의 return 문이 nameof 연산자를 사용하도록 바꿉니다.

// POST: api/TodoItems
[HttpPost]
public async Task<ActionResult<TodoItem>> PostTodoItem(TodoItem todoItem)
{
    _context.TodoItems.Add(todoItem);
    await _context.SaveChangesAsync();

    //return CreatedAtAction("GetTodoItem", new { id = todoItem.Id }, todoItem);
    return CreatedAtAction(nameof(GetTodoItem), new { id = todoItem.Id }, todoItem);
}

위의 코드는 [HttpPost] 특성으로 표시되는 HTTP POST 메서드입니다. 이 메서드는 HTTP 요청 본문에서 할 일 항목 값을 가져옵니다.

자세한 내용은 Http[동사] 특성을 사용한 특성 라우팅을 참조하세요.

CreatedAtAction 메서드는 다음 작업을 수행합니다.

  • 성공 시 HTTP 201 상태 코드를 반환합니다. HTTP 201은 서버에서 새 리소스를 만드는 HTTP POST 메서드의 표준 응답입니다.
  • 응답에 대한 위치 헤더를 추가합니다. Location 헤더는 새로 만들어진 할 일 항목의 URI를 지정합니다. 자세한 내용은 10.2.2 201 생성됨을 참조하세요.
  • Location 헤더의 URI를 만들려면 GetTodoItem 작업을 참조합니다. C# nameof 키워드는 CreatedAtAction 호출에서 작업 이름의 하드 코딩을 방지하는 데 사용됩니다.

Postman을 설치합니다.

이 자습서에서는 Postman을 사용하여 웹 API를 테스트합니다.

  • Postman을 설치합니다.
  • 웹앱을 시작합니다.
  • Postman을 시작합니다.
  • SSL 인증서 확인을 사용하지 않도록 설정합니다.
    • Windows용 Postman: Windows용 Postman 파일>설정(일반 탭)에서 SSL 인증서 확인을 사용하지 않도록 설정합니다.
    • macOS용 Postman: Windows용 Postman Postman>설정(일반 탭)에서 SSL 인증서 확인을 사용하지 않도록 설정합니다.

      경고

      컨트롤러를 테스트한 후에 SSL 인증서 확인을 다시 사용하도록 설정합니다.

Postman을 사용하여 PostTodoItem 테스트

  • 새 요청을 만듭니다.

  • HTTP 메서드를 POST로 설정합니다.

  • URI를 https://localhost:<port>/api/todoitems로 설정합니다. 예: https://localhost:5001/api/todoitems.

  • 본문 탭을 선택합니다.

  • 원시 라디오 단추를 선택합니다.

  • 유형을 JSON(application/json)으로 설정합니다.

  • 요청 본문에서 할 일 항목에 대한 JSON을 입력합니다.

    {
      "name":"walk dog",
      "isComplete":true
    }
    
  • 보내기를 선택합니다.

    Postman with create request

Postman을 사용하여 위치 헤더 URI 테스트

  • 응답 창에서 헤더 탭을 선택합니다.

  • 위치 헤더 값을 복사합니다.

    Headers tab of the Postman console

  • HTTP 메서드를 GET로 설정합니다.

  • URI를 https://localhost:<port>/api/todoitems/1로 설정합니다. 예: https://localhost:5001/api/todoitems/1.

  • 보내기를 선택합니다.

GET 메서드 검사

다음과 같은 메서드는 두 개의 GET 엔드포인트를 구현합니다.

  • GET /api/todoitems
  • GET /api/todoitems/{id}

브라우저 또는 Postman에서 두 개의 엔드포인트를 호출하여 앱을 테스트합니다. 예를 들어:

  • https://localhost:5001/api/todoitems
  • https://localhost:5001/api/todoitems/1

GetTodoItems를 호출하면 다음과 비슷한 응답이 생성됩니다.

[
  {
    "id": 1,
    "name": "Item1",
    "isComplete": false
  }
]

Postman을 사용하여 Get 테스트

  • 새 요청을 만듭니다.
  • HTTP 메서드를 GET으로 설정합니다.
  • 요청 URI를 https://localhost:<port>/api/todoitems로 설정합니다. 예: https://localhost:5001/api/todoitems.
  • Postman에서 두 개의 창 보기를 설정합니다.
  • 보내기를 선택합니다.

이 앱은 메모리 내 데이터베이스를 사용합니다. 앱이 중지된 후 시작되면 이전 GET 요청이 데이터를 반환하지 않습니다. 데이터가 반환되지 않으면 앱에 데이터를 POST합니다.

라우팅 및 URL 경로

[HttpGet] 특성은 HTTP GET 요청에 응답하는 메서드를 나타냅니다. 각 방법에 대한 URL 경로는 다음과 같이 구성됩니다.

  • 컨트롤러의 Route 특성에서 템플릿 문자열로 시작합니다.

    [Route("api/[controller]")]
    [ApiController]
    public class TodoItemsController : ControllerBase
    {
        private readonly TodoContext _context;
    
        public TodoItemsController(TodoContext context)
        {
            _context = context;
        }
    
  • [controller]를 컨트롤러의 이름으로 바꿉니다. 일반적으로 컨트롤러 클래스 이름에서 "Controller" 접미사를 뺀 이름입니다. 이 샘플의 경우 컨트롤러 클래스 이름은 TodoItemsController이므로 컨트롤러 이름은 “TodoItems”입니다. ASP.NET Core 라우팅은 대/소문자를 구분하지 않습니다.

  • [HttpGet] 특성에 경로 템플릿(예: [HttpGet("products")])이 있는 경우 경로에 추가합니다. 이 샘플은 템플릿을 사용하지 않습니다. 자세한 내용은 Http[동사] 특성을 사용한 특성 라우팅을 참조하세요.

다음 GetTodoItem 메서드에서 "{id}"는 할 일 항목의 고유 식별자에 대한 자리 표시자 변수입니다. GetTodoItem이 호출되면 URL의 "{id}" 값을 id 매개 변수의 메서드에 제공합니다.

// GET: api/TodoItems/5
[HttpGet("{id}")]
public async Task<ActionResult<TodoItem>> GetTodoItem(long id)
{
    var todoItem = await _context.TodoItems.FindAsync(id);

    if (todoItem == null)
    {
        return NotFound();
    }

    return todoItem;
}

반환 값

GetTodoItemsGetTodoItem 메서드의 반환 형식은 ActionResult<T> 형식입니다. ASP.NET Core는 자동으로 JSON에 개체를 직렬화하고 JSON을 응답 메시지의 본문에 기록합니다. 이 반환 형식의 응답 코드는 200이며 처리되지 않은 예외가 없다고 가정합니다. 처리되지 않은 예외는 5xx 오류로 변환됩니다.

ActionResult 반환 형식은 다양한 HTTP 상태 코드를 나타낼 수 있습니다. 예를 들어 GetTodoItem은 두 가지 상태 값을 반환할 수 있습니다.

  • 요청된 ID와 일치하는 항목이 없는 경우 메서드가 404 NotFound 오류 코드를 반환합니다.
  • 그렇지 않으면 메서드가 JSON 응답 본문에서 200을 반환합니다. item을 반환하면 HTTP 200 응답이 발생합니다.

PutTodoItem 메서드

다음과 같이 PutTodoItem 메서드를 검사합니다.

// PUT: api/TodoItems/5
[HttpPut("{id}")]
public async Task<IActionResult> PutTodoItem(long id, TodoItem todoItem)
{
    if (id != todoItem.Id)
    {
        return BadRequest();
    }

    _context.Entry(todoItem).State = EntityState.Modified;

    try
    {
        await _context.SaveChangesAsync();
    }
    catch (DbUpdateConcurrencyException)
    {
        if (!TodoItemExists(id))
        {
            return NotFound();
        }
        else
        {
            throw;
        }
    }

    return NoContent();
}

HTTP PUT을 사용하는 것을 제외하고 PutTodoItemPostTodoItem와 비슷합니다. 응답은 204(콘텐츠 없음)입니다. HTTP 사양에 따라 PUT 요청의 경우 클라이언트는 변경 내용만이 아니라 전체 업데이트된 엔터티를 보내야 합니다. 부분 업데이트를 지원하려면 HTTP PATCH를 사용합니다.

PutTodoItem을 호출하는 중 오류가 발생하면 GET을 호출하여 데이터베이스에 항목이 있는지 확인합니다.

PutTodoItem 메서드 테스트

이 샘플은 앱이 시작될 때마다 초기화되어야 하는 메모리 내 데이터베이스를 사용합니다. PUT 호출을 실행하기 전에 데이터베이스에 항목이 있어야 합니다. GET을 호출하여 PUT 호출을 실행하기 전에 데이터베이스에 항목이 있는지 확인합니다.

ID = 1인 할 일 항목을 업데이트하고 해당 이름을 “feed fish”로 설정합니다.

  {
    "id":1,
    "name":"feed fish",
    "isComplete":true
  }

다음 이미지는 Postman 업데이트를 보여줍니다.

Postman console showing 204 (No Content) response

DeleteTodoItem 메서드

다음과 같이 DeleteTodoItem 메서드를 검사합니다.

// DELETE: api/TodoItems/5
[HttpDelete("{id}")]
public async Task<ActionResult<TodoItem>> DeleteTodoItem(long id)
{
    var todoItem = await _context.TodoItems.FindAsync(id);
    if (todoItem == null)
    {
        return NotFound();
    }

    _context.TodoItems.Remove(todoItem);
    await _context.SaveChangesAsync();

    return todoItem;
}

DeleteTodoItem 메서드 테스트

Postman을 사용하여 할 일 항목을 삭제합니다.

  • 메서드를 DELETE로 설정합니다.
  • 예를 들어 삭제할 개체의 URI를 https://localhost:5001/api/todoitems/1로 설정합니다.
  • 보내기를 선택합니다.

과도한 게시 방지

현재 샘플 앱은 전체 TodoItem 개체를 공개합니다. 일반적으로 프로덕션 앱은 모델의 하위 집합을 사용하여 입력 및 반환되는 데이터를 제한합니다. 이 동작에는 여러 가지 이유가 있으며, 보안이 주요 이유 중 하나입니다. 일반적으로 모델의 하위 집합을 DTO(데이터 전송 개체), 입력 모델 또는 뷰 모델이라고 합니다. 이 문서에서는 DTO를 사용합니다.

DTO는 다음과 같은 용도로 사용할 수 있습니다.

  • 과도한 게시를 방지합니다.
  • 클라이언트에 표시되지 않아야 하는 속성을 숨깁니다.
  • 페이로드 크기를 줄이기 위해 일부 속성을 생략합니다.
  • 중첩된 개체를 포함하는 개체 그래프를 평면화합니다. 클라이언트에는 평면화된 개체 그래프가 더 편리할 수 있습니다.

DTO 방법을 설명하려면 비밀 필드를 포함하도록 TodoItem 클래스를 업데이트합니다.

public class TodoItem
{
    public long Id { get; set; }
    public string Name { get; set; }
    public bool IsComplete { get; set; }
    public string Secret { get; set; }
}

이 앱에서는 숨겨진 필드를 숨겨야 하지만, 관리 앱은 숨겨진 필드를 공개할 수 있습니다.

비밀 필드를 게시하고 가져올 수 있는지 확인합니다.

DTO 모델을 만듭니다.

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

TodoItemDTO를 사용하도록 TodoItemsController를 업데이트합니다.

    [HttpGet]
    public async Task<ActionResult<IEnumerable<TodoItemDTO>>> GetTodoItems()
    {
        return await _context.TodoItems
            .Select(x => ItemToDTO(x))
            .ToListAsync();
    }

    [HttpGet("{id}")]
    public async Task<ActionResult<TodoItemDTO>> GetTodoItem(long id)
    {
        var todoItem = await _context.TodoItems.FindAsync(id);

        if (todoItem == null)
        {
            return NotFound();
        }

        return ItemToDTO(todoItem);
    }

    [HttpPut("{id}")]
    public async Task<IActionResult> UpdateTodoItem(long id, TodoItemDTO todoItemDTO)
    {
        if (id != todoItemDTO.Id)
        {
            return BadRequest();
        }

        var todoItem = await _context.TodoItems.FindAsync(id);
        if (todoItem == null)
        {
            return NotFound();
        }

        todoItem.Name = todoItemDTO.Name;
        todoItem.IsComplete = todoItemDTO.IsComplete;

        try
        {
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException) when (!TodoItemExists(id))
        {
            return NotFound();
        }

        return NoContent();
    }

    [HttpPost]
    public async Task<ActionResult<TodoItemDTO>> CreateTodoItem(TodoItemDTO todoItemDTO)
    {
        var todoItem = new TodoItem
        {
            IsComplete = todoItemDTO.IsComplete,
            Name = todoItemDTO.Name
        };

        _context.TodoItems.Add(todoItem);
        await _context.SaveChangesAsync();

        return CreatedAtAction(
            nameof(GetTodoItem),
            new { id = todoItem.Id },
            ItemToDTO(todoItem));
    }

    [HttpDelete("{id}")]
    public async Task<IActionResult> DeleteTodoItem(long id)
    {
        var todoItem = await _context.TodoItems.FindAsync(id);

        if (todoItem == null)
        {
            return NotFound();
        }

        _context.TodoItems.Remove(todoItem);
        await _context.SaveChangesAsync();

        return NoContent();
    }

    private bool TodoItemExists(long id) =>
         _context.TodoItems.Any(e => e.Id == id);

    private static TodoItemDTO ItemToDTO(TodoItem todoItem) =>
        new TodoItemDTO
        {
            Id = todoItem.Id,
            Name = todoItem.Name,
            IsComplete = todoItem.IsComplete
        };       
}

비밀 필드를 게시하거나 가져올 수 없음을 확인합니다.

JavaScript를 사용하여 웹 API 호출

자습서: JavaScript로 ASP.NET Core 웹 API 호출하기를 참조하세요.

웹 API에 인증 지원 추가

ASP.NET Core Identity는 ASP.NET Core 웹앱에 UI(사용자 인터페이스) 로그인 기능을 추가합니다. 웹 API 및 SPA를 보호하려면 다음 중 하나를 사용합니다.

Duende Identity Server는 ASP.NET Core용 OpenID Connect 및 OAuth 2.0 프레임워크입니다. Duende Identity Server에서는 다음과 같은 보안 기능을 사용할 수 있습니다.

  • AaaS(Authentication as a Service)
  • 여러 응용 프로그램 유형에 대한 SSO(Single Sign-On/Off)
  • API에 대한 액세스 제어
  • 페더레이션 게이트웨이

중요

Duende Software에서 Duende 서버의 프로덕션 사용에 대한 라이선스 요금 지불을 요구할 수 있습니다. 자세한 내용은 ASP.NET Core 5.0에서 6.0으로 마이그레이션을 참조하세요.

자세한 내용은 Duende Server 설명서(Duende Software 웹 사이트)를 참조하세요.

Azure에 게시

Azure에 배포에 대한 자세한 내용은 빠른 시작: ASP.NET 웹앱 배포를 참조하세요.

추가 리소스

이 자습서에서 샘플 코드 보기 또는 다운로드 다운로드하는 방법을 참조하세요.

자세한 내용은 다음 자료를 참조하세요.