Zugreifen auf einen OData-Dienst über einen .NET-Client (C#)

von Mike Wasson

Abgeschlossenes Projekt herunterladen

In diesem Tutorial wird gezeigt, wie Sie einen OData-Dienst aus einer C#-Clientanwendung aufrufen.

Im Tutorial verwendete Softwareversionen

In diesem Tutorial erfahren Sie, wie Sie eine Clientanwendung erstellen, die einen OData-Dienst aufruft. Der OData-Dienst macht die folgenden Entitäten verfügbar:

  • Product
  • Supplier
  • ProductRating

Diagramm, das die O Data-Dienstentitäten und eine Liste ihrer Eigenschaften zeigt, mit Verbindungspfeilen, um zu zeigen, wie die einzelnen Daten in Beziehung stehen oder zusammenarbeiten.

In den folgenden Artikeln wird beschrieben, wie Sie den OData-Dienst in der Web-API implementieren. (Sie müssen sie jedoch nicht lesen, um dieses Tutorial zu verstehen.)

Generieren des Dienstproxys

Der erste Schritt besteht darin, einen Dienstproxy zu generieren. Der Dienstproxy ist eine .NET-Klasse, die Methoden für den Zugriff auf den OData-Dienst definiert. Der Proxy übersetzt Methodenaufrufe in HTTP-Anforderungen.

Diagramm, das die H T T P P-Anforderungsaufrufe des Dienstproxys zeigt, die von der Anwendung, über den Dienstproxy und an den O Data-Dienst hin- und her ausgeführt werden.

Öffnen Sie zunächst das OData-Dienstprojekt in Visual Studio. Drücken Sie STRG+F5, um den Dienst lokal in IIS Express auszuführen. Notieren Sie sich die lokale Adresse, einschließlich der Portnummer, die Visual Studio zuweist. Sie benötigen diese Adresse, wenn Sie den Proxy erstellen.

Öffnen Sie als Nächstes eine weitere instance von Visual Studio, und erstellen Sie ein Konsolenanwendungsprojekt. Die Konsolenanwendung ist unsere OData-Clientanwendung. (Sie können das Projekt auch derselben Projektmappe wie der Dienst hinzufügen.)

Hinweis

Die verbleibenden Schritte beziehen sich auf das Konsolenprojekt.

Klicken Sie in Projektmappen-Explorer mit der rechten Maustaste auf Verweise, und wählen Sie Dienstverweis hinzufügen aus.

Screenshot des Fensters des Projektmappen-Explorers mit dem Menü unter

Geben Sie im Dialogfeld Dienstverweis hinzufügen die Adresse des OData-Diensts ein:

http://localhost:port/odata

Dabei ist Port die Portnummer.

Screenshot des Fensters

Geben Sie unter Namespace "ProductService" ein. Diese Option definiert den Namespace der Proxyklasse.

Klicken Sie auf Start. Visual Studio liest das OData-Metadatendokument, um die Entitäten im Dienst zu ermitteln.

Screenshot des Dialogfelds

Klicken Sie auf OK , um die Proxyklasse ihrem Projekt hinzuzufügen.

Screenshot des Dialogfelds

Erstellen einer Instanz der Dienstproxyklasse

Erstellen Sie in Ihrer Main -Methode wie folgt eine neue instance der Proxyklasse:

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

            // ...
        }
    }
}

Verwenden Sie erneut die tatsächliche Portnummer, unter der Ihr Dienst ausgeführt wird. Wenn Sie Ihren Dienst bereitstellen, verwenden Sie den URI des Livediensts. Sie müssen den Proxy nicht aktualisieren.

Der folgende Code fügt einen Ereignishandler hinzu, der die Anforderungs-URIs im Konsolenfenster ausgibt. Dieser Schritt ist nicht erforderlich, aber es ist interessant, die URIs für jede Abfrage anzuzeigen.

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

Abfragen des Diensts

Der folgende Code ruft die Liste der Produkte aus dem OData-Dienst ab.

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

Beachten Sie, dass Sie keinen Code schreiben müssen, um die HTTP-Anforderung zu senden oder die Antwort zu analysieren. Die Proxyklasse führt dies automatisch aus, wenn Sie die Container.Products Auflistung in der foreach-Schleife auflisten.

Wenn Sie die Anwendung ausführen, sollte die Ausgabe wie folgt aussehen:

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

Verwenden Sie eine -Klausel, um eine where Entität nach ID abzurufen.

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

Im weiteren Verlauf dieses Themas wird nicht die gesamte Main Funktion angezeigt, nur der Code, der zum Aufrufen des Diensts benötigt wird.

Anwenden von Abfrageoptionen

OData definiert Abfrageoptionen , die zum Filtern, Sortieren, Seitendaten usw. verwendet werden können. Im Dienstproxy können Sie diese Optionen mithilfe verschiedener LINQ-Ausdrücke anwenden.

In diesem Abschnitt zeige ich kurze Beispiele. Weitere Informationen finden Sie im Thema LINQ Considerations (WCF Data Services) auf MSDN.

Filtern ($filter)

Verwenden Sie zum Filtern eine where -Klausel. Im folgenden Beispiel wird nach Produktkategorie gefiltert.

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

Dieser Code entspricht der folgenden OData-Abfrage.

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

Beachten Sie, dass der Proxy die where -Klausel in einen OData-Ausdruck $filter konvertiert.

Sortieren ($orderby)

Verwenden Sie zum Sortieren eine orderby -Klausel. Im folgenden Beispiel wird nach Preis sortiert, vom höchsten zum niedrigsten Preis.

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

Hier ist die entsprechende OData-Anforderung.

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

Client-Side Paging ($skip und $top)

Bei großen Entitätssätzen möchte der Client möglicherweise die Anzahl der Ergebnisse einschränken. Ein Client kann beispielsweise 10 Einträge gleichzeitig anzeigen. Dies wird als clientseitiges Paging bezeichnet. (Es gibt auch serverseitiges Paging, bei dem der Server die Anzahl der Ergebnisse einschränkt.) Verwenden Sie zum Ausführen des clientseitigen Pagings die LINQ-Methoden Skip und Take . Im folgenden Beispiel werden die ersten 40 Ergebnisse übersprungen und die nächsten 10 verwendet.

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

Hier ist die entsprechende OData-Anforderung:

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

Auswählen ($select) und Erweitern ($expand)

Verwenden Sie die DataServiceQuery<t>.Expand -Methode, um verwandte Entitäten einzuschließen. So können Sie z. B. die Supplier für jedes Producteinschließen:

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

Hier ist die entsprechende OData-Anforderung:

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

Verwenden Sie die LINQ select-Klausel , um die Form der Antwort zu ändern. Im folgenden Beispiel wird nur der Name jedes Produkts ohne andere Eigenschaften abgerufen.

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

Hier ist die entsprechende OData-Anforderung:

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

Eine select-Klausel kann verwandte Entitäten enthalten. Rufen Sie in diesem Fall nicht Expand auf. Der Proxy schließt in diesem Fall automatisch die Erweiterung ein. Im folgenden Beispiel werden der Name und der Lieferant jedes Produkts abgerufen.

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

Hier ist die entsprechende OData-Anforderung. Beachten Sie, dass sie die Option $expand enthält.

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

Weitere Informationen zu $select und $expand finden Sie unter Verwenden von $select, $expand und $value in Web-API 2.

Hinzufügen einer neuen Entität

Um einer Entitätsgruppe eine neue Entität hinzuzufügen, rufen Sie AddToEntitySetauf, wobei EntitySet der Name des Entitätssatzes ist. Fügt dem Entitätssatz Products beispielsweise AddToProducts ein neues Product hinzu. Wenn Sie den Proxy generieren, erstellt WCF Data Services automatisch diese stark typisierten AddTo-Methoden.

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

Um eine Verknüpfung zwischen zwei Entitäten hinzuzufügen, verwenden Sie die Methoden AddLink und SetLink . Der folgende Code fügt einen neuen Lieferanten und ein neues Produkt hinzu und erstellt dann Verknüpfungen zwischen ihnen.

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

Verwenden Sie AddLink , wenn die Navigationseigenschaft eine Sammlung ist. In diesem Beispiel fügen wir der Sammlung des Products Lieferanten ein Produkt hinzu.

Verwenden Sie SetLink , wenn die Navigationseigenschaft eine einzelne Entität ist. In diesem Beispiel legen wir die Supplier Eigenschaft für das Produkt fest.

Update/Patch

Um eine Entität zu aktualisieren, rufen Sie die UpdateObject-Methode auf .

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

Das Update wird ausgeführt, wenn Sie SaveChanges aufrufen. Standardmäßig sendet WCF eine HTTP MERGE-Anforderung. Die Option PatchOnUpdate weist WCF an, stattdessen einen HTTP-PATCH zu senden.

Hinweis

Warum PATCH im Vergleich zu MERGE? Die ursprüngliche HTTP 1.1-Spezifikation (RCF 2616) definierte keine HTTP-Methode mit "partieller Aktualisierung"-Semantik. Um partielle Updates zu unterstützen, hat die OData-Spezifikation die MERGE-Methode definiert. 2010 definierte RFC 5789 die PATCH-Methode für partielle Updates. Sie können einige der Verlauf in diesem Blogbeitrag auf dem WCF Data Services Blog lesen. Patch wird heute vor MERGE bevorzugt. Der vom Web-API-Gerüstbau erstellte OData-Controller unterstützt beide Methoden.

Wenn Sie die gesamte Entität (PUT-Semantik) ersetzen möchten, geben Sie die Option ReplaceOnUpdate an. Dies führt dazu, dass WCF eine HTTP-PUT-Anforderung sendet.

container.SaveChanges(SaveChangesOptions.ReplaceOnUpdate);

Löschen einer Entität

Um eine Entität zu löschen, rufen Sie DeleteObject auf.

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

Aufrufen einer OData-Aktion

In OData sind Aktionen eine Möglichkeit, serverseitige Verhaltensweisen hinzuzufügen, die nicht einfach als CRUD-Vorgänge für Entitäten definiert werden können.

Obwohl das OData-Metadatendokument die Aktionen beschreibt, erstellt die Proxyklasse keine stark typisierten Methoden für sie. Sie können weiterhin eine OData-Aktion aufrufen, indem Sie die generische Execute-Methode verwenden . Sie müssen jedoch die Datentypen der Parameter und den Rückgabewert kennen.

Die Aktion verwendet beispielsweise den RateProduct Parameter "Rating" vom Typ Int32 und gibt einen doublezurück. Der folgende Code zeigt, wie Diese Aktion aufgerufen wird.

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

Weitere Informationen finden Sie unterAufrufen von Dienstvorgängen und -aktionen.

Eine Option besteht darin, die Container-Klasse zu erweitern, um eine stark typisierte Methode bereitzustellen, die die Aktion aufruft:

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