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
- Visual Studio 2013 (funktioniert mit Visual Studio 2012)
- WCF Data Services-Clientbibliothek
- Web-API 2. (Der OData-Beispieldienst wird mithilfe der Web-API 2 erstellt, aber die Clientanwendung ist nicht von der Web-API abhängig.)
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
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.)
- Erstellen eines OData-Endpunkts in Der Web-API 2
- OData-Entitätsbeziehungen in Web-API 2
- OData-Aktionen in der Web-API 2
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.
Ö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.
Geben Sie im Dialogfeld Dienstverweis hinzufügen die Adresse des OData-Diensts ein:
http://localhost:port/odata
Dabei ist Port die Portnummer.
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.
Klicken Sie auf OK , um die Proxyklasse ihrem Projekt hinzuzufügen.
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 Product
einschließ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 AddToEntitySet
auf, 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 double
zurü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();
}
}
}