다음을 통해 공유


.NET 클라이언트에서 OData 서비스 호출(C#)

작성자: Mike Wasson

완료된 프로젝트 다운로드

이 자습서에서는 C# 클라이언트 애플리케이션에서 OData 서비스를 호출하는 방법을 보여줍니다.

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

이 자습서에서는 OData 서비스를 호출하는 클라이언트 애플리케이션을 만드는 방법에 대해 살펴보겠습니다. OData 서비스는 다음 엔터티를 노출합니다.

  • Product
  • Supplier
  • ProductRating

연결 화살표가 있는 O Data Service 엔터티 및 해당 속성 목록을 보여 주는 다이어그램은 각 엔터티가 서로 관련되거나 함께 작동하는 방식을 보여 줍니다.

다음 문서에서는 Web API에서 OData 서비스를 구현하는 방법을 설명합니다. (그러나 이 자습서를 이해하기 위해 읽을 필요는 없습니다.)

서비스 프록시 생성

첫 번째 단계는 서비스 프록시를 생성하는 것입니다. 서비스 프록시는 OData 서비스에 액세스하기 위한 메서드를 정의하는 .NET 클래스입니다. 프록시는 메서드 호출을 HTTP 요청으로 변환합니다.

서비스 프록시, 서비스 프록시 및 O Data Service를 통해 애플리케이션에서 앞뒤로 실행되는 서비스 프록시의 H TT P 요청 호출을 보여 주는 다이어그램

Visual Studio에서 OData 서비스 프로젝트를 열어 시작합니다. Ctrl+F5를 눌러 IIS Express 서비스를 로컬로 실행합니다. Visual Studio에서 할당하는 포트 번호를 포함하여 로컬 주소를 확인합니다. 프록시를 만들 때 이 주소가 필요합니다.

다음으로 Visual Studio의 다른 instance 열고 콘솔 애플리케이션 프로젝트를 만듭니다. 콘솔 애플리케이션은 OData 클라이언트 애플리케이션입니다. (서비스와 동일한 솔루션에 프로젝트를 추가할 수도 있습니다.)

참고

나머지 단계는 콘솔 프로젝트를 참조합니다.

솔루션 탐색기 참조를 마우스 오른쪽 단추로 클릭하고 서비스 참조 추가를 선택합니다.

새 서비스 참조를 추가하기 위해 '참조' 아래의 메뉴를 보여 주는 솔루션 탐색기 창의 스크린샷

서비스 참조 추가 대화 상자에서 OData 서비스의 주소를 입력합니다.

http://localhost:port/odata

여기서 port 는 포트 번호입니다.

U R L 주소 필드의 포트 번호와 이름 공간을 추가할 필드를 보여 주는 '서비스 참조 추가' 창의 스크린샷

네임스페이스에 "ProductService"를 입력합니다. 이 옵션은 프록시 클래스의 네임스페이스를 정의합니다.

이동을 클릭합니다. Visual Studio는 OData 메타데이터 문서를 읽어 서비스의 엔터티를 검색합니다.

컨테이너 서비스를 강조 표시하는 '서비스 참조 추가' 대화 상자의 스크린샷에서 실행 중인 작업을 표시합니다.

확인을 클릭하여 프록시 클래스를 프로젝트에 추가합니다.

'제품 서비스 클라이언트' 아래의 메뉴를 표시하고 '제품 서비스' 옵션을 강조 표시하는 솔루션 탐색기 대화 상자의 스크린샷.

서비스 프록시 클래스의 인스턴스 만들기

Main 메서드 내에서 다음과 같이 프록시 클래스의 새 instance 만듭니다.

using System;
using System.Data.Services.Client;
using System.Linq;

namespace Client
{
    class Program
    {
        static void Main(string[] args)
        {
            Uri uri = new Uri("http://localhost:1234/odata/");
            var container = new ProductService.Container(uri);

            // ...
        }
    }
}

다시 말하지만, 서비스가 실행 중인 실제 포트 번호를 사용합니다. 서비스를 배포할 때 라이브 서비스의 URI를 사용합니다. 프록시를 업데이트할 필요가 없습니다.

다음 코드는 요청 URI를 콘솔 창에 출력하는 이벤트 처리기를 추가합니다. 이 단계는 필요하지 않지만 각 쿼리에 대한 URI를 보는 것은 흥미롭습니다.

container.SendingRequest2 += (s, e) =>
{
    Console.WriteLine("{0} {1}", e.RequestMessage.Method, e.RequestMessage.Url);
};

서비스 쿼리

다음 코드는 OData 서비스에서 제품 목록을 가져옵니다.

class Program
{
    static void DisplayProduct(ProductService.Product product)
    {
        Console.WriteLine("{0} {1} {2}", product.Name, product.Price, product.Category);
    }

    // Get an entire entity set.
    static void ListAllProducts(ProductService.Container container)
    {
        foreach (var p in container.Products)
        {
            DisplayProduct(p);
        } 
    }
  
    static void Main(string[] args)
    {
        Uri uri = new Uri("http://localhost:18285/odata/");
        var container = new ProductService.Container(uri);
        container.SendingRequest2 += (s, e) =>
        {
            Console.WriteLine("{0} {1}", e.RequestMessage.Method, e.RequestMessage.Url);
        };

        // Get the list of products
        ListAllProducts(container);
    }
}

HTTP 요청을 보내거나 응답을 구문 분석하는 코드를 작성할 필요가 없습니다. 프록시 클래스는 foreach 루프에서 컬렉션을 열거 Container.Products 할 때 이 작업을 자동으로 수행합니다.

애플리케이션을 실행할 때 출력은 다음과 같아야 합니다.

GET http://localhost:60868/odata/Products
Hat 15.00   Apparel
Scarf   12.00   Apparel
Socks   5.00    Apparel
Yo-yo   4.95    Toys
Puzzle  8.00    Toys

ID로 엔터티를 가져오려면 절을 where 사용합니다.

// Get a single entity.
static void ListProductById(ProductService.Container container, int id)
{
    var product = container.Products.Where(p => p.ID == id).SingleOrDefault();
    if (product != null)
    {
        DisplayProduct(product);
    }
}

이 항목의 나머지 부분에는 전체 Main 함수가 표시되지 않으며 서비스를 호출하는 데 필요한 코드만 표시됩니다.

쿼리 옵션 적용

OData는 필터링, 정렬, 페이지 데이터 등에 사용할 수 있는 쿼리 옵션을 정의합니다. 서비스 프록시에서 다양한 LINQ 식을 사용하여 이러한 옵션을 적용할 수 있습니다.

이 섹션에서는 간단한 예를 보여 줍니다. 자세한 내용은 MSDN의 LINQ 고려 사항(WCF Data Services) 항목을 참조하세요.

필터링($filter)

필터링하려면 절을 where 사용합니다. 다음 예제에서는 제품 범주를 기준으로 필터링합니다.

// Use the $filter option.
static void ListProductsInCategory(ProductService.Container container, string category)
{
    var products =
        from p in container.Products
        where p.Category == category
        select p;
    foreach (var p in products)
    {
        DisplayProduct(p);
    }
}

이 코드는 다음 OData 쿼리에 해당합니다.

GET http://localhost/odata/Products()?$filter=Category eq 'apparel'

프록시는 절을 where OData $filter 식으로 변환합니다.

정렬($orderby)

정렬하려면 절을 orderby 사용합니다. 다음 예제에서는 가격을 기준으로 가장 높은 값에서 가장 낮은 값으로 정렬합니다.

// Use the $orderby option
static void ListProductsSorted(ProductService.Container container)
{
    // Sort by price, highest to lowest.
    var products =
        from p in container.Products
        orderby p.Price descending
        select p;

    foreach (var p in products)
    {
        DisplayProduct(p);
    }
}

해당 OData 요청은 다음과 같습니다.

GET http://localhost/odata/Products()?$orderby=Price desc

Client-Side 페이징($skip 및 $top)

큰 엔터티 집합의 경우 클라이언트는 결과 수를 제한하려고 할 수 있습니다. 예를 들어 클라이언트는 한 번에 10개의 항목을 표시할 수 있습니다. 이를 클라이언트 쪽 페이징이라고 합니다. 서버에서 결과 수를 제한하는 서버 쪽 페이징도 있습니다. 클라이언트 쪽 페이징을 수행하려면 LINQ 건너뛰기가져오기 메서드를 사용합니다. 다음 예제에서는 처음 40개의 결과를 건너뛰고 다음 10을 사용합니다.

// Use $skip and $top options.
static void ListProductsPaged(ProductService.Container container)
{
    var products =
        (from p in container.Products
          orderby p.Price descending
          select p).Skip(40).Take(10);

    foreach (var p in products)
    {
        DisplayProduct(p);
    }
}

해당 OData 요청은 다음과 같습니다.

GET http://localhost/odata/Products()?$orderby=Price desc&$skip=40&$top=10

선택($select) 및 확장($expand)

관련 엔터티를 포함하려면 메서드를 DataServiceQuery<t>.Expand 사용합니다. 예를 들어 각 Product에 대한 을 Supplier 포함합니다.

// Use the $expand option.
static void ListProductsAndSupplier(ProductService.Container container)
{
    var products = container.Products.Expand(p => p.Supplier);
    foreach (var p in products)
    {
        Console.WriteLine("{0}\t{1}\t{2}", p.Name, p.Price, p.Supplier.Name);
    }
}

해당 OData 요청은 다음과 같습니다.

GET http://localhost/odata/Products()?$expand=Supplier

응답의 모양을 변경하려면 LINQ select 절을 사용합니다. 다음 예제에서는 다른 속성이 없는 각 제품의 이름만 가져옵니다.

// Use the $select option.
static void ListProductNames(ProductService.Container container)
{

    var products = from p in container.Products select new { Name = p.Name };
    foreach (var p in products)
    {
        Console.WriteLine(p.Name);
    }
}

해당 OData 요청은 다음과 같습니다.

GET http://localhost/odata/Products()?$select=Name

select 절에는 관련 엔터티가 포함될 수 있습니다. 이 경우 Expand을 호출하지 마세요. 프록시에는 이 경우 확장이 자동으로 포함됩니다. 다음 예제에서는 각 제품의 이름과 공급자를 가져옵니다.

// Use $expand and $select options
static void ListProductNameSupplier(ProductService.Container container)
{
    var products =
        from p in container.Products
        select new
        {
            Name = p.Name,
            Supplier = p.Supplier.Name
        };
    foreach (var p in products)
    {
        Console.WriteLine("{0}\t{1}", p.Name, p.Supplier);
    }
}

해당 OData 요청은 다음과 같습니다. $expand 옵션이 포함되어 있습니다.

GET http://localhost/odata/Products()?$expand=Supplier&$select=Name,Supplier/Name

$select 및 $expand 대한 자세한 내용은 Web API 2에서 $select, $expand 및 $value 사용을 참조하세요.

새 엔터티 추가

엔터티 집합에 새 엔터티를 추가하려면 를 호출 AddToEntitySet합니다. 여기서 EntitySet 은 엔터티 집합의 이름입니다. 예를 들어 는 AddToProducts 엔터티 집합에 새 ProductProducts 추가합니다. 프록시를 생성하면 WCF Data Services 강력한 형식의 AddTo 메서드를 자동으로 만듭니다.

// Add an entity.
static void AddProduct(ProductService.Container container, ProductService.Product product)
{
    container.AddToProducts(product);
    var serviceResponse = container.SaveChanges();
    foreach (var operationResponse in serviceResponse)
    {
        Console.WriteLine(operationResponse.StatusCode);
    }
}

두 엔터티 간에 링크를 추가하려면 AddLinkSetLink 메서드를 사용합니다. 다음 코드는 새 공급 업체와 새 제품을 추가한 다음 둘 사이에 링크를 만듭니다.

// Add entities with links.
static void AddProductWithSupplier(ProductService.Container container, 
    ProductService.Product product, ProductService.Supplier supplier)
{
    container.AddToSuppliers(supplier);
    container.AddToProducts(product);
    container.AddLink(supplier, "Products", product);
    container.SetLink(product, "Supplier", supplier);
    var serviceResponse = container.SaveChanges();
    foreach (var operationResponse in serviceResponse)
    {
        Console.WriteLine(operationResponse.StatusCode);
    }
}

탐색 속성이 컬렉션인 경우 AddLink 를 사용합니다. 이 예제에서는 공급업체의 컬렉션에 Products 제품을 추가합니다.

탐색 속성이 단일 엔터티인 경우 SetLink 를 사용합니다. 이 예제에서는 제품의 속성을 설정합니다 Supplier .

업데이트/패치

엔터티를 업데이트하려면 UpdateObject 메서드를 호출합니다.

static void UpdatePrice(ProductService.Container container, int id, decimal price)
{
    var product = container.Products.Where(p => p.ID == id).SingleOrDefault();
    if (product != null)
    { 
        product.Price = price;
        container.UpdateObject(product);
        container.SaveChanges(SaveChangesOptions.PatchOnUpdate);
    }
}

업데이트는 SaveChanges를 호출할 때 수행됩니다. 기본적으로 WCF는 HTTP MERGE 요청을 보냅니다. PatchOnUpdate 옵션은 WCF에 HTTP PATCH를 대신 보내도록 지시합니다.

참고

PATCH와 MERGE를 비교하는 이유는 무엇인가요? 원래 HTTP 1.1 사양(RCF 2616)은 "부분 업데이트" 의미 체계를 사용하여 HTTP 메서드를 정의하지 않았습니다. 부분 업데이트를 지원하기 위해 OData 사양은 MERGE 메서드를 정의했습니다. 2010년 RFC 5789 는 부분 업데이트에 대한 PATCH 메서드를 정의했습니다. 이 블로그 게시물의 일부 기록은 WCF Data Services 블로그에서 확인할 수 있습니다. 현재 MERGE보다 PATCH가 선호됩니다. Web API 스캐폴딩에서 만든 OData 컨트롤러는 두 메서드를 모두 지원합니다.

전체 엔터티(PUT 의미 체계)를 바꾸려면 ReplaceOnUpdate 옵션을 지정합니다. 이로 인해 WCF는 HTTP PUT 요청을 보냅니다.

container.SaveChanges(SaveChangesOptions.ReplaceOnUpdate);

엔터티 삭제

엔터티를 삭제하려면 DeleteObject를 호출합니다.

static void DeleteProduct(ProductService.Container container, int id)
{
    var product = container.Products.Where(p => p.ID == id).SingleOrDefault();
    if (product != null)
    {
        container.DeleteObject(product);
        container.SaveChanges();
    }
}

OData 작업 호출

OData에서 작업은 엔터티에서 CRUD 작업으로 쉽게 정의되지 않는 서버 쪽 동작을 추가하는 방법입니다.

OData 메타데이터 문서에서 작업을 설명하지만 프록시 클래스는 강력한 형식의 메서드를 만들지 않습니다. 제네릭 Execute 메서드를 사용하여 OData 작업을 계속 호출할 수 있습니다. 그러나 매개 변수의 데이터 형식과 반환 값을 알아야 합니다.

예를 들어 작업은 형식 Int32RateProduct "Rating"이라는 매개 변수를 사용하고 를 반환합니다double. 다음 코드는 이 작업을 호출하는 방법을 보여줍니다.

int rating = 2;
Uri actionUri = new Uri(uri, "Products(5)/RateProduct");
var averageRating = container.Execute<double>(
    actionUri, "POST", true, new BodyOperationParameter("Rating", rating)).First();

자세한 내용은서비스 작업 및 작업 호출을 참조하세요.

한 가지 옵션은 컨테이너 클래스를 확장하여 작업을 호출하는 강력한 형식의 메서드를 제공하는 것입니다.

namespace ProductServiceClient.ProductService
{
    public partial class Container
    {
        public double RateProduct(int productID, int rating)
        {
            Uri actionUri = new Uri(this.BaseUri,
                String.Format("Products({0})/RateProduct", productID)
                );

            return this.Execute<double>(actionUri, 
                "POST", true, new BodyOperationParameter("Rating", rating)).First();
        }
    }
}