.NET 클라이언트에서 OData 서비스 호출(C#)Calling an OData Service From a .NET Client (C#)

Mike Wassonby Mike Wasson

완료 된 프로젝트 다운로드Download Completed Project

이 자습서에서는 C# 클라이언트 응용 프로그램에서 OData 서비스를 호출 하는 방법을 보여 줍니다.This tutorial shows how to call an OData service from a C# client application.

자습서에서 사용 되는 소프트웨어 버전Software versions used in the tutorial

이 자습서에서는 OData 서비스를 호출 하는 클라이언트 응용 프로그램을 만드는 과정을 안내 합니다.In this tutorial, I'll walk through creating a client application that calls an OData service. OData 서비스는 다음 엔터티를 노출 합니다.The OData service exposes the following entities:

  • Product
  • Supplier
  • ProductRating

다음 문서에서는 Web API에서 OData 서비스를 구현 하는 방법을 설명 합니다.The following articles describe how to implement the OData service in Web API. 그러나이 자습서를 이해 하기 위해 읽을 필요는 없습니다.(You don't need to read them to understand this tutorial, however.)

서비스 프록시를 생성 합니다.Generate the Service Proxy

첫 번째 단계는 서비스 프록시를 생성 하는 것입니다.The first step is to generate a service proxy. 서비스 프록시는 OData 서비스에 액세스 하기 위한 메서드를 정의 하는 .NET 클래스입니다.The service proxy is a .NET class that defines methods for accessing the OData service. 프록시는 메서드 호출을 HTTP 요청으로 변환 합니다.The proxy translates method calls into HTTP requests.

Visual Studio에서 OData 서비스 프로젝트를 열어 시작 합니다.Start by opening the OData service project in Visual Studio. IIS Express에서 로컬로 서비스를 실행 하려면 CTRL + F5 키를 누릅니다.Press CTRL+F5 to run the service locally in IIS Express. Visual Studio에서 할당 하는 포트 번호를 포함 하 여 로컬 주소를 확인 합니다.Note the local address, including the port number that Visual Studio assigns. 프록시를 만들 때이 주소가 필요 합니다.You will need this address when you create the proxy.

그런 다음 Visual Studio의 다른 인스턴스를 열고 콘솔 응용 프로그램 프로젝트를 만듭니다.Next, open another instance of Visual Studio and create a console application project. 콘솔 응용 프로그램은 OData 클라이언트 응용 프로그램이 됩니다.The console application will be our OData client application. (서비스와 동일한 솔루션에 프로젝트를 추가할 수도 있습니다.)(You can also add the project to the same solution as the service.)

Note

나머지 단계는 콘솔 프로젝트를 참조 합니다.The remaining steps refer the console project.

솔루션 탐색기에서 참조 를 마우스 오른쪽 단추로 클릭 하 고 서비스 참조 추가을 선택 합니다.In Solution Explorer, right-click References and select Add Service Reference.

서비스 참조 추가 대화 상자에서 OData 서비스의 주소를 입력 합니다.In the Add Service Reference dialog, type the address of the OData service:

http://localhost:port/odata

여기서 port 는 포트 번호입니다.where port is the port number.

네임 스페이스에 "제품 서비스"를 입력 합니다.For Namespace, type "ProductService". 이 옵션은 프록시 클래스의 네임 스페이스를 정의 합니다.This option defines the namespace of the proxy class.

이동을 클릭합니다.Click Go. Visual Studio는 OData 메타 데이터 문서를 읽어 서비스의 엔터티를 검색 합니다.Visual Studio reads the OData metadata document to discover the entities in the service.

확인 을 클릭 하 여 프록시 클래스를 프로젝트에 추가 합니다.Click OK to add the proxy class to your project.

서비스 프록시 클래스의 인스턴스를 만듭니다.Create an Instance of the Service Proxy Class

Main 메서드 내에서 다음과 같이 프록시 클래스의 새 인스턴스를 만듭니다.Inside your Main method, create a new instance of the proxy class, as follows:

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);

            // ...
        }
    }
}

서비스를 실행 하는 실제 포트 번호를 다시 사용 합니다.Again, use the actual port number where your service is running. 서비스를 배포 하는 경우 라이브 서비스의 URI를 사용 합니다.When you deploy your service, you will use the URI of the live service. 프록시를 업데이트할 필요가 없습니다.You don't need to update the proxy.

다음 코드에서는 요청 Uri를 콘솔 창에 출력 하는 이벤트 처리기를 추가 합니다.The following code adds an event handler that prints the request URIs to the console window. 이 단계는 필요 하지 않지만 각 쿼리에 대 한 Uri를 확인 하는 것이 흥미롭습니다.This step isn't required, but it's interesting to see the URIs for each query.

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

서비스 쿼리Query the Service

다음 코드는 OData 서비스에서 제품 목록을 가져옵니다.The following code gets the list of products from the OData service.

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 요청을 보내거나 응답을 구문 분석 하는 코드를 작성할 필요가 없습니다.Notice that you don't need to write any code to send the HTTP request or parse the response. 프록시 클래스는 foreach 루프에서 Container.Products 컬렉션을 열거할 때이를 자동으로 수행 합니다.The proxy class does this automatically when you enumerate the Container.Products collection in the foreach loop.

응용 프로그램을 실행 하는 경우 출력은 다음과 같습니다.When you run the application, the output should look like the following:

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 절을 사용 합니다.To get an entity by ID, use a where clause.

// 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 함수를 표시 하지 않습니다.For the rest of this topic, I won't show the entire Main function, just the code needed to call the service.

쿼리 옵션 적용Apply Query Options

OData는 필터링, 정렬, 페이지 데이터 등에 사용할 수 있는 쿼리 옵션 을 정의 합니다.OData defines query options that can be used to filter, sort, page data, and so forth. 서비스 프록시에서 다양 한 LINQ 식을 사용 하 여 이러한 옵션을 적용할 수 있습니다.In the service proxy, you can apply these options by using various LINQ expressions.

이 섹션에서는 간단한 예를 보여 드리겠습니다.In this section, I'll show brief examples. 자세한 내용은 MSDN의 LINQ 고려 사항 (WCF Data Services) 항목을 참조 하십시오.For more details, see the topic LINQ Considerations (WCF Data Services) on MSDN.

필터링 ($filter)Filtering ($filter)

필터링 하려면 where 절을 사용 합니다.To filter, use a where clause. 다음 예제에서는 제품 범주별로 필터링 합니다.The following example filters by product category.

// 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 쿼리에 해당 합니다.This code corresponds to the following OData query.

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

프록시는 where 절을 OData $filter 식으로 변환 합니다.Notice that the proxy converts the where clause into an OData $filter expression.

정렬 ($orderby)Sorting ($orderby)

정렬 하려면 orderby 절을 사용 합니다.To sort, use an orderby clause. 다음 예에서는 가격을 기준으로 내림차순으로 정렬 합니다.The following example sorts by price, from highest to lowest.

// 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 요청은 다음과 같습니다.Here is the corresponding OData request.

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

클라이언트 쪽 페이징 ($skip 및 $top)Client-Side Paging ($skip and $top)

대량 엔터티 집합의 경우 클라이언트에서 결과 수를 제한할 수 있습니다.For large entity sets, the client might want to limit the number of results. 예를 들어 클라이언트에는 한 번에 10 개의 항목이 표시 될 수 있습니다.For example, a client might show 10 entries at a time. 이를 클라이언트 쪽 페이징이라고 합니다.This is called client-side paging. 서버 쪽 페이징은서버에서 결과 수를 제한 하는 역할도 합니다. 클라이언트 쪽 페이징을 수행 하려면 LINQ SkipTake 메서드를 사용 합니다.(There is also server-side paging, where the server limits the number of results.) To perform client-side paging, use the LINQ Skip and Take methods. 다음 예에서는 첫 번째 40 결과를 건너뛰고 다음 10 개를 사용 합니다.The following example skips the first 40 results and takes the next 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 요청은 다음과 같습니다.Here is the corresponding OData request:

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

($Select)를 선택 하 고 확장 ($expand)Select ($select) and Expand ($expand)

관련 엔터티를 포함 하려면 DataServiceQuery<t>.Expand 메서드를 사용 합니다.To include related entities, use the DataServiceQuery<t>.Expand method. 예를 들어 각 Product에 대 한 Supplier를 포함 하려면 다음을 수행 합니다.For example, to include the Supplier for each Product:

// 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 요청은 다음과 같습니다.Here is the corresponding OData request:

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

응답의 셰이프를 변경 하려면 LINQ select 절을 사용 합니다.To change the shape of the response, use the LINQ select clause. 다음 예에서는 각 제품의 이름만 가져오며 다른 속성은 포함 하지 않습니다.The following example gets just the name of each product, with no other properties.

// 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 요청은 다음과 같습니다.Here is the corresponding OData request:

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

Select 절은 관련 엔터티를 포함할 수 있습니다.A select clause can include related entities. 이 경우에는 확장을 호출 하지 마십시오. 이 경우 프록시는 자동으로 확장을 포함 합니다.In that case, do not call Expand; the proxy automatically includes the expansion in this case. 다음 예에서는 각 제품의 이름과 공급자를 가져옵니다.The following example gets the name and supplier of each product.

// 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 요청은 다음과 같습니다.Here is the corresponding OData request. $Expand 옵션이 포함 되어 있습니다.Notice that it includes the $expand option.

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

$Select 및 $expand에 대 한 자세한 내용은 WEB API 2에서 $select, $expand 및 $Value 사용을 참조 하세요.For more information about $select and $expand, see Using $select, $expand, and $value in Web API 2.

새 엔터티 추가Add a New Entity

엔터티 집합에 새 엔터티를 추가 하려면 AddToEntitySet를 호출 합니다. 여기서 EntitySet 은 엔터티 집합의 이름입니다.To add a new entity to an entity set, call AddToEntitySet, where EntitySet is the name of the entity set. 예를 들어 AddToProductsProducts 엔터티 집합에 새 Product를 추가 합니다.For example, AddToProducts adds a new Product to the Products entity set. 프록시를 생성할 때 WCF Data Services는 이러한 강력한 형식의 AddTo 메서드를 자동으로 만듭니다.When you generate the proxy, WCF Data Services automatically creates these strongly-typed AddTo methods.

// 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 메서드를 사용 합니다.To add a link between two entities, use the AddLink and SetLink methods. 다음 코드는 새 공급 업체와 새 제품을 추가한 다음 두 제품 간에 링크를 만듭니다.The following code adds a new supplier and a new product, and then creates links between them.

// 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 를 사용 합니다.Use AddLink when the navigation property is a collection. 이 예에서는 공급자의 Products 컬렉션에 제품을 추가 합니다.In this example, we are adding a product to the Products collection on the supplier.

탐색 속성이 단일 엔터티인 경우 Setlink 를 사용 합니다.Use SetLink when the navigation property is a single entity. 이 예에서는 제품의 Supplier 속성을 설정 합니다.In this example, we are setting the Supplier property on the product.

업데이트/패치Update / Patch

엔터티를 업데이트 하려면 Updateobject 메서드를 호출 합니다.To update an entity, call the UpdateObject method.

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를 호출 하면 업데이트가 수행 됩니다.The update is performed when you call SaveChanges. 기본적으로 WCF는 HTTP MERGE 요청을 보냅니다.By default, WCF sends an HTTP MERGE request. PatchOnUpdate 옵션은 HTTP 패치를 보내도록 WCF에 지시 합니다.The PatchOnUpdate option tells WCF to send an HTTP PATCH instead.

Note

패치 및 병합 이유Why PATCH versus MERGE? 원래 HTTP 1.1 사양 (RCF 2616)이 "부분 업데이트" 의미 체계를 사용 하 여 http 메서드를 정의 하지 않았습니다.The original HTTP 1.1 specification (RCF 2616) did not define any HTTP method with "partial update" semantics. 부분 업데이트를 지원 하기 위해 OData 사양은 MERGE 메서드를 정의 합니다.To support partial updates, the OData specification defined the MERGE method. 2010에서 RFC 5789 은 부분 업데이트에 대 한 PATCH 메서드를 정의 했습니다.In 2010, RFC 5789 defined the PATCH method for partial updates. WCF Data Services 블로그의이 블로그 게시물 에서 일부 기록을 읽을 수 있습니다.You can read some of the history in this blog post on the WCF Data Services Blog. 현재는 MERGE 보다 패치를 선호 합니다.Today, PATCH is preferred over MERGE. Web API 스 캐 폴딩에서 만든 OData 컨트롤러는 두 가지 방법을 모두 지원 합니다.The OData controller created by the Web API scaffolding supports both methods.

전체 엔터티 (의미 체계 배치)를 대체 하려면 ReplaceOnUpdate 옵션을 지정 합니다.If you want to replace the entire entity (PUT semantics), specify the ReplaceOnUpdate option. 이렇게 하면 WCF에서 HTTP PUT 요청을 보냅니다.This causes WCF to send an HTTP PUT request.

container.SaveChanges(SaveChangesOptions.ReplaceOnUpdate);

엔터티 삭제Delete an Entity

엔터티를 삭제 하려면 DeleteObject를 호출 합니다.To delete an entity, call 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 동작 호출Invoke an OData Action

OData에서 작업 은 엔터티에 대 한 CRUD 작업으로 쉽게 정의 되지 않는 서버 쪽 동작을 추가 하는 방법입니다.In OData, actions are a way to add server-side behaviors that are not easily defined as CRUD operations on entities.

OData 메타 데이터 문서에서 작업을 설명 하지만 프록시 클래스에서 해당 작업에 대 한 강력한 형식의 메서드를 만들지는 않습니다.Although the OData metadata document describes the actions, the proxy class does not create any strongly-typed methods for them. 제네릭 Execute 메서드를 사용 하 여 여전히 OData 작업을 호출할 수 있습니다.You can still invoke an OData action by using the generic Execute method. 그러나 매개 변수의 데이터 형식과 반환 값을 알고 있어야 합니다.However, you will need to know the data types of the parameters and the return value.

예를 들어 RateProduct 작업은 Int32 형식의 "등급" 이라는 매개 변수를 사용 하 고 double을 반환 합니다.For example, the RateProduct action takes parameter named "Rating" of type Int32 and returns a double. 다음 코드에서는이 작업을 호출 하는 방법을 보여 줍니다.The following code shows how to invoke this action.

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

자세한 내용은서비스 작업 및 작업 호출을 참조 하세요.For more information, seeCalling Service Operations and Actions.

한 가지 옵션은 컨테이너 클래스를 확장 하 여 동작을 호출 하는 강력한 형식의 메서드를 제공 하는 것입니다.One option is to extend the Container class to provide a strongly typed method that invokes the action:

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();
        }
    }
}