ASP.NET Web API 1에서 CRUD 작업 사용

작성자: Mike Wasson

완료된 프로젝트 다운로드

이 자습서에서는 ASP.NET 4.x용 ASP.NET Web API 사용하여 HTTP 서비스에서 CRUD 작업을 지원하는 방법을 보여 줍니다.

자습서에서 사용되는 소프트웨어 버전

  • Visual Studio 2012
  • Web API 1(Web API 2에서도 작동)

CRUD는 네 가지 기본 데이터베이스 작업인 "만들기, 읽기, 업데이트 및 삭제"를 의미합니다. 또한 많은 HTTP 서비스는 REST 또는 REST와 같은 API를 통해 CRUD 작업을 모델링합니다.

이 자습서에서는 제품 목록을 관리하는 매우 간단한 웹 API를 빌드합니다. 각 제품에는 이름, 가격 및 범주(예: "장난감" 또는 "하드웨어")와 제품 ID가 포함됩니다.

제품 API는 다음 방법을 노출합니다.

작업 HTTP 메서드 상대 URI
모든 제품 목록 가져오기 GET /api/products
ID별 제품 가져오기 GET /api/products/id
범주별 제품 가져오기 GET /api/products?category=category
새 제품 만들기 POST /api/products
제품 업데이트 PUT /api/products/id
제품 삭제 DELETE /api/products/id

일부 URI에는 경로에 제품 ID가 포함됩니다. 예를 들어 ID가 28인 제품을 가져오기 위해 클라이언트는 에 대한 http://hostname/api/products/28GET 요청을 보냅니다.

리소스

제품 API는 두 가지 리소스 유형에 대한 URI를 정의합니다.

리소스 URI
모든 제품의 목록입니다. /api/products
개별 제품입니다. /api/products/id

메서드

다음과 같이 네 가지 기본 HTTP 메서드(GET, PUT, POST 및 DELETE)를 CRUD 작업에 매핑할 수 있습니다.

  • GET은 지정된 URI에서 리소스의 표현을 검색합니다. GET은 서버에 부작용이 없어야 합니다.
  • PUT은 지정된 URI에서 리소스를 업데이트합니다. 서버에서 클라이언트가 새 URI를 지정할 수 있도록 허용하는 경우 PUT을 사용하여 지정된 URI에서 새 리소스를 만들 수도 있습니다. 이 자습서의 경우 API는 PUT을 통한 만들기를 지원하지 않습니다.
  • POST는 새 리소스를 만듭니다. 서버는 새 개체에 대한 URI를 할당하고 이 URI를 응답 메시지의 일부로 반환합니다.
  • DELETE는 지정된 URI에서 리소스를 삭제합니다.

참고: PUT 메서드는 전체 제품 엔터티를 대체합니다. 즉, 클라이언트는 업데이트된 제품의 전체 표현을 보내야 합니다. 부분 업데이트를 지원하려면 PATCH 메서드를 사용하는 것이 좋습니다. 이 자습서에서는 PATCH를 구현하지 않습니다.

새 Web API 프로젝트 만들기

Visual Studio를 실행하여 시작하고 시작 페이지에서 새 프로젝트를 선택합니다. 또는 파일 메뉴에서 새로 만들기 를 선택한 다음 프로젝트를 선택합니다.

템플릿 창에서 설치된 템플릿을 선택하고 Visual C# 노드를 확장합니다. Visual C#에서 을 선택합니다. 프로젝트 템플릿 목록에서 MVC 4 웹 애플리케이션 ASP.NET 선택합니다. 프로젝트 이름을 "ProductStore"로 지정하고 확인을 클릭합니다.

메뉴 옵션을 보여 주는 새 프로젝트 창의 스크린샷과 A SP 점 NET M V C 4 웹 애플리케이션을 만드는 경로를 강조 표시합니다.

새 ASP.NET MVC 4 프로젝트 대화 상자에서 Web API를 선택하고 확인을 클릭합니다.

사용 가능한 템플릿의 박스형 이미지를 표시하고 Web API 템플릿을 파란색으로 강조 표시하는 새 ASP 점 NET 프로젝트의 스크린샷.

모델 추가

모델은 애플리케이션에서 데이터를 나타내는 개체입니다. ASP.NET Web API 강력한 형식의 CLR 개체를 모델로 사용할 수 있으며 클라이언트의 XML 또는 JSON으로 자동으로 직렬화됩니다.

ProductStore API의 경우 데이터는 제품으로 구성되므로 라는 Product새 클래스를 만듭니다.

솔루션 탐색기가 표시되지 않는 경우 보기 메뉴를 클릭하고 솔루션 탐색기를 선택합니다. 솔루션 탐색기 Models 폴더를 마우스 오른쪽 단추로 클릭합니다. 상황에 맞는 메뉴에서 추가를 선택한 다음, 클래스를 선택합니다. 클래스 이름을 "Product"로 지정합니다.

모델 선택 영역을 강조 표시하여 클래스 추가 옵션을 선택하는 추가 메뉴를 표시하는 솔루션 탐색기 메뉴의 스크린샷

클래스에 다음 속성을 추가합니다 Product .

namespace ProductStore.Models
{
    public class Product
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Category { get; set; }
        public decimal Price { get; set; }
    }
}

리포지토리 추가

제품 컬렉션을 저장해야 합니다. 컬렉션을 서비스 구현과 분리하는 것이 좋습니다. 이렇게 하면 서비스 클래스를 다시 작성하지 않고도 백업 저장소를 변경할 수 있습니다. 이러한 유형의 디자인을 리포지토리 패턴이라고 합니다. 먼저 리포지토리에 대한 제네릭 인터페이스를 정의합니다.

솔루션 탐색기 Models 폴더를 마우스 오른쪽 단추로 클릭합니다. 추가를 선택한 다음, 새 항목을 선택합니다.

모델 옵션을 강조 표시하고 새 항목을 추가하는 메뉴를 표시하는 솔루션 탐색기 메뉴의 스크린샷

템플릿 창에서 설치된 템플릿을 선택하고 C# 노드를 확장합니다. C#에서 코드를 선택합니다. 코드 템플릿 목록에서 인터페이스를 선택합니다. 인터페이스 이름을 "IProductRepository"로 지정합니다.

코드 및 인터페이스 옵션을 회색으로 강조 표시하는 설치된 템플릿 메뉴를 보여 주는 템플릿 창의 스크린샷.

다음 구현을 추가합니다.

namespace ProductStore.Models
{
    public interface IProductRepository
    {
        IEnumerable<Product> GetAll();
        Product Get(int id);
        Product Add(Product item);
        void Remove(int id);
        bool Update(Product item);
    }
}

이제 Models 폴더에 "ProductRepository"라는 다른 클래스를 추가합니다. 이 클래스는 인터페이스를 구현합니다 IProductRepository . 다음 구현을 추가합니다.

namespace ProductStore.Models
{
    public class ProductRepository : IProductRepository
    {
        private List<Product> products = new List<Product>();
        private int _nextId = 1;

        public ProductRepository()
        {
            Add(new Product { Name = "Tomato soup", Category = "Groceries", Price = 1.39M });
            Add(new Product { Name = "Yo-yo", Category = "Toys", Price = 3.75M });
            Add(new Product { Name = "Hammer", Category = "Hardware", Price = 16.99M });
        }

        public IEnumerable<Product> GetAll()
        {
            return products;
        }

        public Product Get(int id)
        {
            return products.Find(p => p.Id == id);
        }

        public Product Add(Product item)
        {
            if (item == null)
            {
                throw new ArgumentNullException("item");
            }
            item.Id = _nextId++;
            products.Add(item);
            return item;
        }

        public void Remove(int id)
        {
            products.RemoveAll(p => p.Id == id);
        }

        public bool Update(Product item)
        {
            if (item == null)
            {
                throw new ArgumentNullException("item");
            }
            int index = products.FindIndex(p => p.Id == item.Id);
            if (index == -1)
            {
                return false;
            }
            products.RemoveAt(index);
            products.Add(item);
            return true;
        }
    }
}

리포지토리는 목록을 로컬 메모리에 유지합니다. 자습서에서는 정상이지만 실제 애플리케이션에서는 데이터베이스 또는 클라우드 스토리지에 데이터를 외부에 저장합니다. 리포지토리 패턴을 사용하면 나중에 구현을 더 쉽게 변경할 수 있습니다.

Web API 컨트롤러 추가

ASP.NET MVC로 작업한 경우 컨트롤러에 이미 익숙합니다. ASP.NET Web API 컨트롤러는 클라이언트의 HTTP 요청을 처리하는 클래스입니다. 새 프로젝트 마법사는 프로젝트를 만들 때 두 개의 컨트롤러를 만들었습니다. 이를 보려면 솔루션 탐색기 Controllers 폴더를 확장합니다.

  • HomeController는 기존의 ASP.NET MVC 컨트롤러입니다. 사이트에 대한 HTML 페이지를 제공하는 역할을 하며 웹 API와 직접 관련이 없습니다.
  • ValuesController는 WebAPI 컨트롤러의 예입니다.

솔루션 탐색기 파일을 마우스 오른쪽 단추로 클릭하고 삭제를 선택하여 ValuesController를 삭제합니다. 이제 다음과 같이 새 컨트롤러를 추가합니다.

솔루션 탐색기에서 Controllers 폴더를 마우스 오른쪽 단추로 클릭합니다. 추가를 선택한 후 컨트롤러를 선택합니다.

컨트롤러를 추가할 경로를 강조 표시하는 다른 메뉴를 제공하는 컨트롤러 범주를 강조 표시하는 솔루션 탐색기 메뉴의 스크린샷

컨트롤러 추가 마법사에서 컨트롤러 이름을 "ProductsController"로 지정합니다. 템플릿 드롭다운 목록에서 빈 API 컨트롤러를 선택합니다. 그런 다음, 추가를 클릭합니다.

스캐폴딩 옵션 아래에 이름을 입력할 컨트롤러 이름 필드와 드롭다운 템플릿 목록을 보여 주는 컨트롤러 추가 창의 스크린샷

참고

컨트롤러를 Controllers라는 폴더에 배치할 필요는 없습니다. 폴더 이름은 중요하지 않습니다. 단순히 원본 파일을 구성하는 편리한 방법입니다.

컨트롤러 추가 마법사는 Controllers 폴더에 ProductsController.cs라는 파일을 만듭니다. 이 파일이 열려 있지 않으면 파일을 두 번 클릭하여 엽니다. 다음 using 문을 추가합니다.

using ProductStore.Models;

IProductRepository instance 포함하는 필드를 추가합니다.

public class ProductsController : ApiController
{
    static readonly IProductRepository repository = new ProductRepository();
}

참고

컨트롤러에서 를 호출 new ProductRepository() 하는 것은 컨트롤러를 의 IProductRepository특정 구현과 연결하기 때문에 최상의 디자인이 아닙니다. 더 나은 방법은 Web API 종속성 확인자 사용을 참조하세요.

리소스 가져오기

ProductStore API는 여러 "읽기" 작업을 HTTP GET 메서드로 노출합니다. 각 작업은 클래스의 메서드에 해당합니다 ProductsController .

작업 HTTP 메서드 상대 URI
모든 제품 목록 가져오기 GET /api/products
ID별 제품 가져오기 GET /api/products/id
범주별 제품 가져오기 GET /api/products?category=category

모든 제품 목록을 얻으려면 클래스에 다음 메서드를 ProductsController 추가합니다.

public class ProductsController : ApiController
{
    public IEnumerable<Product> GetAllProducts()
    {
        return repository.GetAll();
    }
    // ....
}

메서드 이름은 "Get"으로 시작하므로 규칙에 따라 GET 요청에 매핑됩니다. 또한 메서드에 매개 변수가 없으므로 경로에 "id" 세그먼트가 포함되지 않은 URI에 매핑됩니다.

ID로 제품을 가져오려면 클래스에 다음 메서드를 ProductsController 추가합니다.

public Product GetProduct(int id)
{
    Product item = repository.Get(id);
    if (item == null)
    {
        throw new HttpResponseException(HttpStatusCode.NotFound); 
    }
    return item;
}

이 메서드 이름도 "Get"으로 시작하지만 메서드에는 id라는 매개 변수가 있습니다. 이 매개 변수는 URI 경로의 "id" 세그먼트에 매핑됩니다. ASP.NET Web API 프레임워크는 매개 변수에 대한 올바른 데이터 형식(int)으로 ID를 자동으로 변환합니다.

Id가 유효하지 않으면 GetProduct 메서드는 HttpResponseException 형식의 예외를 throw합니다. 이 예외는 프레임워크에서 404(찾을 수 없음) 오류로 변환됩니다.

마지막으로 범주별로 제품을 찾는 메서드를 추가합니다.

public IEnumerable<Product> GetProductsByCategory(string category)
{
    return repository.GetAll().Where(
        p => string.Equals(p.Category, category, StringComparison.OrdinalIgnoreCase));
}

요청 URI에 쿼리 문자열이 있는 경우 Web API는 쿼리 매개 변수를 컨트롤러 메서드의 매개 변수와 일치시키려고 시도합니다. 따라서 "api/products?category=category" 형식의 URI가 이 메서드에 매핑됩니다.

리소스 만들기

다음으로, 클래스에 메서드를 ProductsController 추가하여 새 제품을 만듭니다. 메서드의 간단한 구현은 다음과 같습니다.

// Not the final implementation!
public Product PostProduct(Product item)
{
    item = repository.Add(item);
    return item;
}

이 메서드에 대한 두 가지 사항에 유의하세요.

  • 메서드 이름은 "Post..."로 시작합니다. 새 제품을 만들기 위해 클라이언트는 HTTP POST 요청을 보냅니다.
  • 메서드는 Product 형식의 매개 변수를 사용합니다. Web API에서 복합 형식의 매개 변수는 요청 본문에서 역직렬화됩니다. 따라서 클라이언트는 XML 또는 JSON 형식으로 제품 개체의 직렬화된 표현을 보낼 것으로 예상합니다.

이 구현은 작동하지만 완료되지는 않습니다. HTTP 응답에 다음이 포함되도록 하는 것이 가장 좋습니다.

  • 응답 코드: 기본적으로 Web API 프레임워크는 응답 상태 코드를 200(확인)으로 설정합니다. 그러나 HTTP/1.1 프로토콜에 따르면 POST 요청으로 인해 리소스가 생성되면 서버는 상태 201(생성됨)으로 회신해야 합니다.
  • 위치: 서버가 리소스를 만들 때 응답의 위치 헤더에 새 리소스의 URI를 포함해야 합니다.

ASP.NET Web API HTTP 응답 메시지를 쉽게 조작할 수 있습니다. 향상된 구현은 다음과 같습니다.

public HttpResponseMessage PostProduct(Product item)
{
    item = repository.Add(item);
    var response = Request.CreateResponse<Product>(HttpStatusCode.Created, item);

    string uri = Url.Link("DefaultApi", new { id = item.Id });
    response.Headers.Location = new Uri(uri);
    return response;
}

이제 메서드 반환 형식이 HttpResponseMessage입니다. Product 대신 HttpResponseMessage를 반환하면 상태 코드 및 위치 헤더를 포함하여 HTTP 응답 메시지의 세부 정보를 제어할 수 있습니다.

CreateResponse 메서드는 HttpResponseMessage를 만들고 Product 개체의 직렬화된 표현을 응답 메시지의 본문에 자동으로 씁니다.

참고

이 예제에서는 의 유효성을 Product검사하지 않습니다. 모델 유효성 검사에 대한 자세한 내용은 ASP.NET Web API 모델 유효성 검사를 참조하세요.

리소스 업데이트

PUT을 사용하여 제품을 업데이트하는 것은 간단합니다.

public void PutProduct(int id, Product product)
{
    product.Id = id;
    if (!repository.Update(product))
    {
        throw new HttpResponseException(HttpStatusCode.NotFound);
    }
}

메서드 이름은 "Put..."으로 시작하므로 Web API는 PUT 요청과 일치합니다. 메서드는 제품 ID와 업데이트된 제품의 두 매개 변수를 사용합니다. id 매개 변수는 URI 경로에서 가져오고 제품 매개 변수는 요청 본문에서 역직렬화됩니다. 기본적으로 ASP.NET Web API 프레임워크는 경로에서 간단한 매개 변수 형식을 사용하고 요청 본문의 복합 형식을 사용합니다.

리소스 삭제

리소스를 삭제하려면 "삭제..."를 정의합니다. 메서드.

public void DeleteProduct(int id)
{
    Product item = repository.Get(id);
    if (item == null)
    {
        throw new HttpResponseException(HttpStatusCode.NotFound);
    }

    repository.Remove(id);
}

DELETE 요청이 성공하면 상태 설명하는 엔터티 본문을 사용하여 상태 200(OK)을 반환할 수 있습니다. 삭제가 아직 보류 중인 경우 상태 202(수락됨) 또는 엔터티 본문이 없는 상태 204(콘텐츠 없음)입니다. 이 경우 메서드에는 DeleteProduct 반환 형식이 void 있으므로 ASP.NET Web API 자동으로 이를 상태 코드 204(콘텐츠 없음)로 변환합니다.