Volání služby OData z klienta .NET (C#)
Mike Wasson
Tento kurz ukazuje, jak volat službu OData z klientské aplikace jazyka C#.
Verze softwaru použité v kurzu
- Visual Studio 2013 (funguje se sadou Visual Studio 2012)
- Klientská knihovna pro WCF Data Services
- Webové rozhraní API 2. (Ukázková služba OData je vytvořená pomocí webového rozhraní API 2, ale klientská aplikace nezávisí na webovém rozhraní API.)
V tomto kurzu vás provedeme vytvořením klientské aplikace, která volá službu OData. Služba OData zveřejňuje následující entity:
Product
Supplier
ProductRating
Následující články popisují, jak implementovat službu OData ve webovém rozhraní API. (K pochopení tohoto kurzu je ale nemusíte číst.)
- Vytvoření koncového bodu OData ve webovém rozhraní API 2
- Vztahy entit OData ve webovém rozhraní API 2
- Akce OData ve webovém rozhraní API 2
Vygenerování proxy služby
Prvním krokem je vygenerování proxy serveru služby. Proxy služby je třída .NET, která definuje metody pro přístup ke službě OData. Proxy překládá volání metod na požadavky HTTP.
Začněte otevřením projektu služby OData v sadě Visual Studio. Stisknutím kombinace kláves CTRL+F5 spusťte službu místně v IIS Express. Poznamenejte si místní adresu, včetně čísla portu, které sada Visual Studio přiřadí. Tuto adresu budete potřebovat při vytváření proxy serveru.
Dále otevřete další instanci sady Visual Studio a vytvořte projekt konzolové aplikace. Konzolová aplikace bude naše klientská aplikace OData. (Projekt můžete také přidat do stejného řešení jako služba.)
Poznámka
Zbývající kroky se týkají projektu konzoly.
V Průzkumník řešení klikněte pravým tlačítkem na Odkazy a vyberte Přidat odkaz na službu.
V dialogovém okně Přidat odkaz na službu zadejte adresu služby OData:
http://localhost:port/odata
kde port je číslo portu.
Jako Obor názvů zadejte ProductService. Tato možnost definuje obor názvů třídy proxy.
Klikněte na Přejít. Visual Studio přečte dokument metadat OData, aby zjistil entity ve službě.
Kliknutím na OK přidejte třídu proxy do projektu.
Vytvoření instance třídy proxy služby
Main
V metodě vytvořte novou instanci třídy proxy následujícím způsobem:
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);
// ...
}
}
}
Znovu použijte skutečné číslo portu, na kterém je vaše služba spuštěná. Při nasazování služby použijete identifikátor URI živé služby. Nemusíte aktualizovat proxy server.
Následující kód přidá obslužnou rutinu události, která vypíše identifikátory URI požadavku do okna konzoly. Tento krok není povinný, ale je zajímavé vidět identifikátory URI pro každý dotaz.
container.SendingRequest2 += (s, e) =>
{
Console.WriteLine("{0} {1}", e.RequestMessage.Method, e.RequestMessage.Url);
};
Dotaz na službu
Následující kód získá seznam produktů ze služby 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);
}
}
Všimněte si, že k odeslání požadavku HTTP nebo parsování odpovědi nemusíte psát žádný kód. Třída proxy to dělá automaticky při výčtu Container.Products
kolekce ve smyčce foreach .
Po spuštění aplikace by výstup měl vypadat takto:
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
Pokud chcete získat entitu podle ID, použijte 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);
}
}
Ve zbývající části tohoto tématu nebudu zobrazovat celou Main
funkci, ale jenom kód potřebný k volání služby.
Použít možnosti dotazu
OData definuje možnosti dotazu , které se dají použít k filtrování, řazení, stránkování dat atd. V proxy serveru služby můžete tyto možnosti použít pomocí různých výrazů LINQ.
V této části ukážu stručné příklady. Další podrobnosti najdete v tématu Aspekty LINQ (WCF Data Services) na webu MSDN.
Filtrování ($filter)
K filtrování použijte klauzuli where
. Následující příklad filtruje podle kategorie produktu.
// 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);
}
}
Tento kód odpovídá následujícímu dotazu OData.
GET http://localhost/odata/Products()?$filter=Category eq 'apparel'
Všimněte si, že proxy převede klauzuli na where
výraz OData $filter
.
Řazení ($orderby)
K řazení použijte klauzuli orderby
. Následující příklad seřadí podle ceny od nejvyšší po nejnižší.
// 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);
}
}
Tady je odpovídající požadavek OData.
GET http://localhost/odata/Products()?$orderby=Price desc
stránkování Client-Side ($skip a $top)
U velkých sad entit může klient chtít omezit počet výsledků. Klient může například zobrazit 10 položek najednou. To se označuje jako stránkování na straně klienta. (K dispozici je také stránkování na straně serveru, kde server omezuje počet výsledků.) Pokud chcete provést stránkování na straně klienta, použijte metody LINQ Skip a Take . Následující příklad přeskočí prvních 40 výsledků a vezme dalších 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);
}
}
Tady je odpovídající požadavek OData:
GET http://localhost/odata/Products()?$orderby=Price desc&$skip=40&$top=10
Výběr ($select) a rozbalení ($expand)
Pokud chcete zahrnout související entity, použijte metodu DataServiceQuery<t>.Expand
. Pokud chcete například pro každou z nich Product
zahrnout 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);
}
}
Tady je odpovídající požadavek OData:
GET http://localhost/odata/Products()?$expand=Supplier
Pokud chcete změnit tvar odpovědi, použijte klauzuli LINQ select . Následující příklad získá pouze název každého produktu bez dalších vlastností.
// 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);
}
}
Tady je odpovídající požadavek OData:
GET http://localhost/odata/Products()?$select=Name
Klauzule select může obsahovat související entity. V takovém případě nevolejte Expand; proxy v tomto případě rozšíření automaticky zahrne. Následující příklad získá název a dodavatele každého 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);
}
}
Tady je odpovídající požadavek OData. Všimněte si, že obsahuje možnost $expand .
GET http://localhost/odata/Products()?$expand=Supplier&$select=Name,Supplier/Name
Další informace o $select a $expand najdete v tématu Použití $select, $expand a $value ve webovém rozhraní API 2.
Přidání nové entity
Pokud chcete do sady entit přidat novou entitu, zavolejte AddToEntitySet
, kde EntitySet je název sady entit. AddToProducts
Například přidá nový Product
do Products
sady entit. Když vygenerujete proxy server, WCF Data Services automaticky vytvoří tyto metody AddTo silného typu.
// 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);
}
}
Pokud chcete přidat propojení mezi dvě entity, použijte metody AddLink a SetLink . Následující kód přidá nového dodavatele a nový produkt a vytvoří mezi nimi propojení.
// 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 použijte, pokud je vlastnost navigace kolekce. V tomto příkladu přidáváme produkt do Products
kolekce u dodavatele.
SetLink použijte, pokud je vlastnost navigace jedna entita. V tomto příkladu Supplier
nastavujeme vlastnost produktu.
Aktualizace nebo oprava
Chcete-li aktualizovat entitu, zavolejte Metodu 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);
}
}
Aktualizace se provede při volání SaveChanges. Ve výchozím nastavení wcf odešle požadavek HTTP MERGE. Možnost PatchOnUpdate informuje wcf, aby místo toho odeslala http PATCH.
Poznámka
Proč PATCH versus MERGE? Původní specifikace HTTP 1.1 (RCF 2616) nedefinovala žádnou metodu HTTP se sémantikou "částečné aktualizace". Pro podporu částečných aktualizací definovala specifikace OData metodu MERGE. V roce 2010 rfc 5789 definoval metodu PATCH pro částečné aktualizace. Některé z historie si můžete přečíst v tomto blogovém příspěvku na WCF Data Services Blogu. Dnes je patch upřednostňován před sloučením. Kontroler OData vytvořený generováním uživatelského rozhraní Web API podporuje obě metody.
Pokud chcete nahradit celou entitu (sémantiku PUT), zadejte možnost ReplaceOnUpdate . To způsobí, že WCF odešle požadavek HTTP PUT.
container.SaveChanges(SaveChangesOptions.ReplaceOnUpdate);
Odstranění entity
Pokud chcete odstranit entitu, zavolejte 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();
}
}
Vyvolání akce OData
V OData jsou akce způsob, jak přidat chování na straně serveru, které není snadné definovat jako operace CRUD u entit.
Přestože dokument metadat OData popisuje akce, třída proxy pro ně nevytvoří žádné metody silného typu. Akci OData můžete přesto vyvolat pomocí obecné metody Execute . Budete ale muset znát datové typy parametrů a návratové hodnoty.
Akce například RateProduct
přijme parametr s názvem Rating typu Int32
a vrátí double
hodnotu . Následující kód ukazuje, jak tuto akci vyvolat.
int rating = 2;
Uri actionUri = new Uri(uri, "Products(5)/RateProduct");
var averageRating = container.Execute<double>(
actionUri, "POST", true, new BodyOperationParameter("Rating", rating)).First();
Další informace najdete v tématuVolání operací a akcí služby.
Jednou z možností je rozšířit třídu Container tak, aby poskytovala metodu silného typu, která vyvolá akci:
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();
}
}
}