Udostępnij za pośrednictwem


Wywoływanie usługi protokołu OData z klienta .NET (C#)

Autor: Mike Wasson

Pobieranie ukończonego projektu

W tym samouczku pokazano, jak wywołać usługę OData z poziomu aplikacji klienckiej języka C#.

Wersje oprogramowania używane w samouczku

  • Visual Studio 2013 (działa z programem Visual Studio 2012)
  • Biblioteka klienta usług danych WCF
  • Internetowy interfejs API 2. (Przykładowa usługa OData jest tworzona przy użyciu internetowego interfejsu API 2, ale aplikacja kliencka nie zależy od internetowego interfejsu API).

W tym samouczku omówimy tworzenie aplikacji klienckiej, która wywołuje usługę OData. Usługa OData uwidacznia następujące jednostki:

  • Product
  • Supplier
  • ProductRating

Diagram przedstawiający jednostki usługi Danych O i listę ich właściwości z strzałkami łączącymi, aby pokazać, jak poszczególne jednostki są powiązane lub współdziałają ze sobą.

W poniższych artykułach opisano sposób implementowania usługi OData w internetowym interfejsie API. (Nie musisz ich jednak czytać, aby zrozumieć ten samouczek).

Generowanie serwera proxy usługi

Pierwszym krokiem jest wygenerowanie serwera proxy usługi. Serwer proxy usługi to klasa platformy .NET, która definiuje metody uzyskiwania dostępu do usługi OData. Serwer proxy tłumaczy wywołania metody na żądania HTTP.

Diagram przedstawiający wywołania żądania H T T P serwera proxy usługi działające tam iz powrotem z aplikacji, za pośrednictwem serwera proxy usługi i do usługi danych O.

Rozpocznij od otwarcia projektu usługi OData w programie Visual Studio. Naciśnij klawisze CTRL+F5, aby uruchomić usługę lokalnie w IIS Express. Zanotuj adres lokalny, w tym numer portu przypisany przez program Visual Studio. Ten adres będzie potrzebny podczas tworzenia serwera proxy.

Następnie otwórz kolejne wystąpienie programu Visual Studio i utwórz projekt aplikacji konsolowej. Aplikacja konsolowa będzie aplikacją kliencą OData. (Możesz również dodać projekt do tego samego rozwiązania co usługa).

Uwaga

Pozostałe kroki odwołują się do projektu konsoli.

W Eksplorator rozwiązań kliknij prawym przyciskiem myszy pozycję Odwołania i wybierz polecenie Dodaj odwołanie do usługi.

Zrzut ekranu przedstawiający okno Eksploratora rozwiązań z menu w obszarze

W oknie dialogowym Dodawanie odwołania do usługi wpisz adres usługi OData:

http://localhost:port/odata

gdzie port to numer portu.

Zrzut ekranu przedstawiający okno

W polu Przestrzeń nazw wpisz "ProductService". Ta opcja definiuje przestrzeń nazw klasy proxy.

Kliknij pozycję Przejdź. Program Visual Studio odczytuje dokument metadanych OData, aby odnaleźć jednostki w usłudze.

Zrzut ekranu przedstawiający okno dialogowe

Kliknij przycisk OK , aby dodać klasę serwera proxy do projektu.

Zrzut ekranu przedstawiający okno dialogowe Eksploratora rozwiązań z menu w obszarze

Tworzenie wystąpienia klasy serwera proxy usługi

Main Wewnątrz metody utwórz nowe wystąpienie klasy serwera proxy w następujący sposób:

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

            // ...
        }
    }
}

Ponownie użyj rzeczywistego numeru portu, w którym jest uruchomiona usługa. Podczas wdrażania usługi użyjesz identyfikatora URI usługi na żywo. Nie musisz aktualizować serwera proxy.

Poniższy kod dodaje procedurę obsługi zdarzeń, która wyświetla identyfikatory URI żądań w oknie konsoli. Ten krok nie jest wymagany, ale warto zobaczyć identyfikatory URI dla każdego zapytania.

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

Wykonywanie zapytań względem usługi

Poniższy kod pobiera listę produktów z usługi 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);
    }
}

Zwróć uwagę, że nie musisz pisać żadnego kodu, aby wysłać żądanie HTTP lub przeanalizować odpowiedź. Klasa serwera proxy wykonuje to automatycznie podczas wyliczania Container.Products kolekcji w pętli foreach .

Po uruchomieniu aplikacji dane wyjściowe powinny wyglądać następująco:

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

Aby uzyskać jednostkę według identyfikatora, użyj klauzuli 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);
    }
}

W pozostałej części tego tematu nie pokażę całej Main funkcji — tylko kod potrzebny do wywołania usługi.

Zastosuj opcje zapytania

OData definiuje opcje zapytania , które mogą służyć do filtrowania, sortowania, danych strony itd. Na serwerze proxy usługi można zastosować te opcje przy użyciu różnych wyrażeń LINQ.

W tej sekcji pokażę krótkie przykłady. Aby uzyskać więcej informacji, zobacz temat LINQ Considerations (Usługi danych programu WCF) w witrynie MSDN.

Filtrowanie ($filter)

Aby filtrować, użyj klauzuli where . Poniższy przykład filtruje według kategorii produktów.

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

Ten kod odpowiada następującemu zapytaniu OData.

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

Zwróć uwagę, że serwer proxy konwertuje klauzulę where na wyrażenie OData $filter .

Sortowanie ($orderby)

Aby posortować, użyj klauzuli orderby . Poniższy przykład sortuje według ceny od najwyższej do najniższej.

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

Oto odpowiednie żądanie OData.

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

Client-Side stronicowanie ($skip i $top)

W przypadku dużych zestawów jednostek klient może chcieć ograniczyć liczbę wyników. Na przykład klient może wyświetlać 10 wpisów jednocześnie. Jest to nazywane stronicowaniem po stronie klienta. (Istnieje również stronicowanie po stronie serwera, gdzie serwer ogranicza liczbę wyników). Aby wykonać stronicowanie po stronie klienta, użyj metod POmiń i Take LINQ. Poniższy przykład pomija pierwsze 40 wyników i przyjmuje kolejne 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);
    }
}

Oto odpowiednie żądanie OData:

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

Wybierz ($select) i rozwiń ($expand)

Aby uwzględnić powiązane jednostki, użyj DataServiceQuery<t>.Expand metody . Aby na przykład uwzględnić element dla każdego Productelementu 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);
    }
}

Oto odpowiednie żądanie OData:

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

Aby zmienić kształt odpowiedzi, użyj klauzuli LINQ select . Poniższy przykład pobiera tylko nazwę każdego produktu bez innych właściwości.

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

Oto odpowiednie żądanie OData:

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

Klauzula select może zawierać powiązane jednostki. W takim przypadku nie należy wywoływać funkcji Expand; serwer proxy automatycznie uwzględnia rozszerzenie w tym przypadku. Poniższy przykład pobiera nazwę i dostawcę każdego produktu.

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

Oto odpowiednie żądanie OData. Zwróć uwagę, że zawiera ona opcję $expand .

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

Aby uzyskać więcej informacji na temat $select i $expand, zobacz Using $select, $expand and $value in Web API 2 (Używanie $select, $expand i $value w internetowym interfejsie API 2).

Dodawanie nowej jednostki

Aby dodać nową jednostkę do zestawu jednostek, wywołaj metodę AddToEntitySet, gdzie EntitySet jest nazwą zestawu jednostek. Na przykład AddToProducts dodaje nowy Product element do Products zestawu jednostek. Podczas generowania serwera proxy Usługi danych programu WCF automatycznie tworzy te silnie typizowane metody 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);
    }
}

Aby dodać łącze między dwiema jednostkami, użyj metod AddLink i SetLink . Poniższy kod dodaje nowego dostawcę i nowego produktu, a następnie tworzy łącza między nimi.

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

Użyj polecenia AddLink , gdy właściwość nawigacji jest kolekcją. W tym przykładzie dodajemy produkt do Products kolekcji dostawcy.

Użyj polecenia SetLink , gdy właściwość nawigacji jest pojedynczą jednostką. W tym przykładzie ustawiamy Supplier właściwość produktu.

Aktualizacja/poprawka

Aby zaktualizować jednostkę, wywołaj metodę 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);
    }
}

Aktualizacja jest wykonywana po wywołaniu metody SaveChanges. Domyślnie program WCF wysyła żądanie SCALANIA HTTP. Opcja PatchOnUpdate informuje WCF, aby zamiast tego wysłać poprawkę HTTP.

Uwaga

Dlaczego FUNKCJA PATCH a SCALANIE? Oryginalna specyfikacja HTTP 1.1 (RCF 2616) nie zdefiniowała żadnej metody HTTP z semantykami "częściowej aktualizacji". Aby obsługiwać aktualizacje częściowe, specyfikacja OData zdefiniowała metodę MERGE. W 2010 r. RFC 5789 zdefiniował metodę PATCH dla częściowych aktualizacji. Niektóre z historii można przeczytać w tym wpisie w blogu na blogu Usługi danych programu WCF Blog. Obecnie funkcja PATCH jest preferowana w przypadku scalania. Kontroler OData utworzony przez szkielet internetowego interfejsu API obsługuje obie metody.

Jeśli chcesz zastąpić całą jednostkę (semantyka PUT), określ opcję ReplaceOnUpdate . Powoduje to wysłanie żądania HTTP PUT w programie WCF.

container.SaveChanges(SaveChangesOptions.ReplaceOnUpdate);

Usuwanie jednostki

Aby usunąć jednostkę, wywołaj metodę 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();
    }
}

Wywoływanie akcji OData

W usłudze OData akcje są sposobem dodawania zachowań po stronie serwera, które nie są łatwo zdefiniowane jako operacje CRUD na jednostkach.

Chociaż dokument metadanych OData opisuje akcje, klasa serwera proxy nie tworzy dla nich żadnych silnie typiicznych metod. Nadal można wywołać akcję OData przy użyciu ogólnej metody Execute . Należy jednak znać typy danych parametrów i wartość zwracaną.

Na przykład akcja RateProduct przyjmuje parametr o nazwie "Ocena" typu Int32 i zwraca wartość double. Poniższy kod pokazuje, jak wywołać tę akcję.

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

Aby uzyskać więcej informacji, zobaczWywoływanie operacji i akcji usługi.

Jedną z opcji jest rozszerzenie klasy Container w celu zapewnienia silnie typizowanej metody, która wywołuje akcję:

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