.NET 클라이언트에서 Web API 호출(C#)

이 콘텐츠는 이전 버전의 .NET용입니다. 새 개발은 ASP.NET Core 사용해야 합니다. ASP.NET Core Web API 사용에 대한 자세한 내용은 다음을 참조하세요.

완료된 프로젝트를 다운로드합니다.

이 자습서에서는 System.Net.Http.HttpClient를 사용하여 .NET 애플리케이션에서 웹 API를 호출하는 방법을 보여줍니다.

이 자습서에서는 다음 웹 API를 사용하는 클라이언트 앱이 작성됩니다.

작업 HTTP 메서드 상대 URI
ID별 제품 가져오기 GET /api/products/id
새 제품 만들기 POST /api/products
제품 업데이트 PUT /api/products/id
제품 삭제 DELETE /api/products/id

ASP.NET Web API 사용하여 이 API를 구현하는 방법을 알아보려면 CRUD 작업을 지원하는 웹 API 만들기를 참조하세요.

간단히 하기 위해 이 자습서의 클라이언트 애플리케이션은 Windows 콘솔 애플리케이션입니다. HttpClient는 Windows Phone 및 Windows 스토어 앱에도 지원됩니다. 자세한 내용은 이식 가능한 라이브러리를 사용하여 여러 플랫폼에 대한 Web API 클라이언트 코드 작성을 참조하세요.

참고: 기본 URL 및 상대 URI를 하드 코딩된 값으로 전달하는 경우 API를 활용하기 HttpClient 위한 규칙을 염두에 두어야 합니다. 속성은 HttpClient.BaseAddress 후행 슬래시(/)가 있는 주소로 설정해야 합니다. 예를 들어 하드 코딩된 리소스 URI를 메서드에 HttpClient.GetAsync 전달할 때 선행 슬래시를 포함하지 마세요. ID로 를 가져오려면 다음을 Product 수행합니다.

  1. client.BaseAddress = new Uri("https://localhost:5001/"); 설정
  2. 를 요청합니다 Product. 예: client.GetAsync<Product>("api/products/4");.

콘솔 응용 프로그램 만들기

Visual Studio에서 HttpClientSample 이라는 새 Windows 콘솔 앱을 만들고 다음 코드를 붙여넣습니다.

using System;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;

namespace HttpClientSample
{
    public class Product
    {
        public string Id { get; set; }
        public string Name { get; set; }
        public decimal Price { get; set; }
        public string Category { get; set; }
    }

    class Program
    {
        static HttpClient client = new HttpClient();

        static void ShowProduct(Product product)
        {
            Console.WriteLine($"Name: {product.Name}\tPrice: " +
                $"{product.Price}\tCategory: {product.Category}");
        }

        static async Task<Uri> CreateProductAsync(Product product)
        {
            HttpResponseMessage response = await client.PostAsJsonAsync(
                "api/products", product);
            response.EnsureSuccessStatusCode();

            // return URI of the created resource.
            return response.Headers.Location;
        }

        static async Task<Product> GetProductAsync(string path)
        {
            Product product = null;
            HttpResponseMessage response = await client.GetAsync(path);
            if (response.IsSuccessStatusCode)
            {
                product = await response.Content.ReadAsAsync<Product>();
            }
            return product;
        }

        static async Task<Product> UpdateProductAsync(Product product)
        {
            HttpResponseMessage response = await client.PutAsJsonAsync(
                $"api/products/{product.Id}", product);
            response.EnsureSuccessStatusCode();

            // Deserialize the updated product from the response body.
            product = await response.Content.ReadAsAsync<Product>();
            return product;
        }

        static async Task<HttpStatusCode> DeleteProductAsync(string id)
        {
            HttpResponseMessage response = await client.DeleteAsync(
                $"api/products/{id}");
            return response.StatusCode;
        }

        static void Main()
        {
            RunAsync().GetAwaiter().GetResult();
        }

        static async Task RunAsync()
        {
            // Update port # in the following line.
            client.BaseAddress = new Uri("http://localhost:64195/");
            client.DefaultRequestHeaders.Accept.Clear();
            client.DefaultRequestHeaders.Accept.Add(
                new MediaTypeWithQualityHeaderValue("application/json"));

            try
            {
                // Create a new product
                Product product = new Product
                {
                    Name = "Gizmo",
                    Price = 100,
                    Category = "Widgets"
                };

                var url = await CreateProductAsync(product);
                Console.WriteLine($"Created at {url}");

                // Get the product
                product = await GetProductAsync(url.PathAndQuery);
                ShowProduct(product);

                // Update the product
                Console.WriteLine("Updating price...");
                product.Price = 80;
                await UpdateProductAsync(product);

                // Get the updated product
                product = await GetProductAsync(url.PathAndQuery);
                ShowProduct(product);

                // Delete the product
                var statusCode = await DeleteProductAsync(product.Id);
                Console.WriteLine($"Deleted (HTTP Status = {(int)statusCode})");

            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
            }

            Console.ReadLine();
        }
    }
}

앞의 코드는 전체 클라이언트 앱입니다.

RunAsync 가 실행되고 완료될 때까지 차단됩니다. 대부분의 HttpClient 메서드는 네트워크 I/O를 수행하기 때문에 비동기입니다. 모든 비동기 작업은 내에서 RunAsync수행됩니다. 일반적으로 앱은 기본 스레드를 차단하지 않지만 이 앱은 상호 작용을 허용하지 않습니다.

static async Task RunAsync()
{
    // Update port # in the following line.
    client.BaseAddress = new Uri("http://localhost:64195/");
    client.DefaultRequestHeaders.Accept.Clear();
    client.DefaultRequestHeaders.Accept.Add(
        new MediaTypeWithQualityHeaderValue("application/json"));

    try
    {
        // Create a new product
        Product product = new Product
        {
            Name = "Gizmo",
            Price = 100,
            Category = "Widgets"
        };

        var url = await CreateProductAsync(product);
        Console.WriteLine($"Created at {url}");

        // Get the product
        product = await GetProductAsync(url.PathAndQuery);
        ShowProduct(product);

        // Update the product
        Console.WriteLine("Updating price...");
        product.Price = 80;
        await UpdateProductAsync(product);

        // Get the updated product
        product = await GetProductAsync(url.PathAndQuery);
        ShowProduct(product);

        // Delete the product
        var statusCode = await DeleteProductAsync(product.Id);
        Console.WriteLine($"Deleted (HTTP Status = {(int)statusCode})");

    }
    catch (Exception e)
    {
        Console.WriteLine(e.Message);
    }

    Console.ReadLine();
}

Web API 클라이언트 라이브러리 설치

NuGet 패키지 관리자를 사용하여 Web API 클라이언트 라이브러리 패키지를 설치합니다.

도구 메뉴에서 NuGet 패키지 관리자>패키지 관리자 콘솔을 선택합니다. PMC(패키지 관리자 콘솔)에서 다음 명령을 입력합니다.

Install-Package Microsoft.AspNet.WebApi.Client

위의 명령은 프로젝트에 다음 NuGet 패키지를 추가합니다.

  • Microsoft.AspNet.WebApi.Client
  • Newtonsoft.Json

Newtonsoft.Json(Json.NET 이라고도 함)은 .NET에 널리 사용되는 고성능 JSON 프레임워크입니다.

모델 클래스 추가

Product 클래스를 확인합니다.

public class Product
{
    public string Id { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
    public string Category { get; set; }
}

이 클래스는 웹 API에서 사용하는 데이터 모델과 일치합니다. 앱은 HttpClient를 사용하여 HTTP 응답에서 instance 읽을 Product 수 있습니다. 앱은 역직렬화 코드를 작성할 필요가 없습니다.

HttpClient 만들기 및 초기화

정적 HttpClient 속성을 검사합니다.

static HttpClient client = new HttpClient();

HttpClient 는 애플리케이션의 수명 동안 한 번 인스턴스화하고 다시 사용하도록 하기 위한 것입니다. 다음 조건으로 인해 SocketException 오류가 발생할 수 있습니다.

  • 요청당 새 HttpClient instance 만들기
  • 부하가 많은 서버.

요청당 새 HttpClient instance 만들면 사용 가능한 소켓이 소진됩니다.

다음 코드는 HttpClient instance 초기화합니다.

static async Task RunAsync()
{
    // Update port # in the following line.
    client.BaseAddress = new Uri("http://localhost:64195/");
    client.DefaultRequestHeaders.Accept.Clear();
    client.DefaultRequestHeaders.Accept.Add(
        new MediaTypeWithQualityHeaderValue("application/json"));

앞의 코드가 하는 역할은 다음과 같습니다.

  • HTTP 요청에 대한 기본 URI를 설정합니다. 포트 번호를 서버 앱에서 사용되는 포트로 변경합니다. 서버 앱에 대한 포트를 사용하지 않으면 앱이 작동하지 않습니다.
  • Accept 헤더를 "application/json"으로 설정합니다. 이 헤더를 설정하면 서버에 JSON 형식으로 데이터를 보내도록 지시합니다.

리소스를 검색하는 GET 요청 보내기

다음 코드는 제품에 대한 GET 요청을 보냅니다.

static async Task<Product> GetProductAsync(string path)
{
    Product product = null;
    HttpResponseMessage response = await client.GetAsync(path);
    if (response.IsSuccessStatusCode)
    {
        product = await response.Content.ReadAsAsync<Product>();
    }
    return product;
}

GetAsync 메서드는 HTTP GET 요청을 보냅니다. 메서드가 완료되면 HTTP 응답을 포함하는 HttpResponseMessage 를 반환합니다. 응답의 상태 코드가 성공 코드인 경우 응답 본문에는 제품의 JSON 표현이 포함됩니다. ReadAsync를 호출하여 JSON 페이로드 Product 를 instance 역직렬화합니다. 응답 본문이 임의로 클 수 있으므로 ReadAsync 메서드는 비동기적입니다.

HTTP 응답에 오류 코드가 포함된 경우 HttpClient는 예외를 throw하지 않습니다. 대신 상태 오류 코드인 경우 IsSuccessStatusCode 속성은 false입니다. HTTP 오류 코드를 예외로 처리하려는 경우 응답 개체에서 HttpResponseMessage.EnsureSuccessStatusCode 를 호출합니다. EnsureSuccessStatusCode는 상태 코드가 200-299 범위를 벗어나면 예외를 throw합니다. HttpClient는 요청 시간이 초과되는 경우와 같은 다른 이유로 예외를 throw할 수 있습니다.

역직렬화할 포맷터 Media-Type

매개 변수 없이 ReadAsync 가 호출되면 기본 미디어 포맷터 집합을 사용하여 응답 본문을 읽습니다. 기본 포맷터는 JSON, XML 및 Form-url로 인코딩된 데이터를 지원합니다.

기본 포맷터를 사용하는 대신 ReadAsync 메서드에 포맷터 목록을 제공할 수 있습니다. 사용자 지정 미디어 형식 포맷터가 있는 경우 포맷터 목록을 사용하는 것이 유용합니다.

var formatters = new List<MediaTypeFormatter>() {
    new MyCustomFormatter(),
    new JsonMediaTypeFormatter(),
    new XmlMediaTypeFormatter()
};
resp.Content.ReadAsAsync<IEnumerable<Product>>(formatters);

자세한 내용은 ASP.NET Web API 2의 미디어 포맷터를 참조하세요.

POST 요청을 전송하여 리소스 만들기

다음 코드는 JSON 형식의 instance 포함하는 Product POST 요청을 보냅니다.

static async Task<Uri> CreateProductAsync(Product product)
{
    HttpResponseMessage response = await client.PostAsJsonAsync(
        "api/products", product);
    response.EnsureSuccessStatusCode();

    // return URI of the created resource.
    return response.Headers.Location;
}

PostAsJsonAsync 메서드:

  • 개체를 JSON으로 직렬화합니다.
  • POST 요청에서 JSON 페이로드를 보냅니다.

요청이 성공하면 다음을 수행합니다.

  • 201(만든) 응답을 반환해야 합니다.
  • 응답에는 Location 헤더에 생성된 리소스의 URL이 포함되어야 합니다.

리소스를 업데이트하기 위한 PUT 요청 보내기

다음 코드는 제품 업데이트에 대한 PUT 요청을 보냅니다.

static async Task<Product> UpdateProductAsync(Product product)
{
    HttpResponseMessage response = await client.PutAsJsonAsync(
        $"api/products/{product.Id}", product);
    response.EnsureSuccessStatusCode();

    // Deserialize the updated product from the response body.
    product = await response.Content.ReadAsAsync<Product>();
    return product;
}

PutAsJsonAsync 메서드는 POST 대신 PUT 요청을 전송한다는 점을 제외하고 PostAsJsonAsync처럼 작동합니다.

리소스 삭제를 위한 DELETE 요청 보내기

다음 코드는 제품을 삭제하기 위한 DELETE 요청을 보냅니다.

static async Task<HttpStatusCode> DeleteProductAsync(string id)
{
    HttpResponseMessage response = await client.DeleteAsync(
        $"api/products/{id}");
    return response.StatusCode;
}

GET과 마찬가지로 DELETE 요청에는 요청 본문이 없습니다. DELETE를 사용하여 JSON 또는 XML 형식을 지정할 필요가 없습니다.

샘플 테스트

클라이언트 앱을 테스트하려면 다음을 수행합니다.

  1. 서버 앱을 다운로드하고 실행합니다. 서버 앱이 작동하는지 확인합니다. 예를 들어 는 http://localhost:64195/api/products 제품 목록을 반환해야 합니다.

  2. HTTP 요청에 대한 기본 URI를 설정합니다. 포트 번호를 서버 앱에 사용되는 포트로 변경합니다.

    static async Task RunAsync()
    {
        // Update port # in the following line.
        client.BaseAddress = new Uri("http://localhost:64195/");
        client.DefaultRequestHeaders.Accept.Clear();
        client.DefaultRequestHeaders.Accept.Add(
            new MediaTypeWithQualityHeaderValue("application/json"));
    
  3. 클라이언트 앱을 실행합니다. 다음 출력이 생성됩니다.

    Created at http://localhost:64195/api/products/4
    Name: Gizmo     Price: 100.0    Category: Widgets
    Updating price...
    Name: Gizmo     Price: 80.0     Category: Widgets
    Deleted (HTTP Status = 204)