V minulém článku jsme si vytvořili jednoduchou službu, která obsahovala jednu metodu Products. Dnes si ukážeme jak vytvořit klienta, který nám umožní volat jednotlivé metody na straně služby.
Pro vytvoření klienta máme několik možností, můžeme vužít WebChannelFactory, HttpWebRequest nebo WebClient Avšak dopuručené je použití třídy HttpClient. Tato třída je součástí knihovny WCF Rest Starter Kit Preview 2.
Vytvoříme si tedy Consolovou aplikaci v mém případě pojmenovanou Eshop.Client a přidáme potřebné reference na
- Microsoft.Http
- Microsoft.HttpExtensions
Obě tyto knihovny najdeme po nainstalování WCF Rest Starter Kitu ve složce C:\Program Files\Microsoft WCF REST\WCF REST Starter Kit Preview 2\Assemblies\ Dále musíme ve vlastnostech projektu změnit cílový framework na “.NET 4” z původního “.NET 4 client profile”. Je to z toho důvodu, že třída HttpClient má nějaké závislosti na plném .NETu 4
Nyní se můžeme pustit do vytvoření klienta. Třída HttpClient nabízí 3 způsoby jak nainicializovat náš objekt
- HttpClient()
- HttpClient(string baseAddress) – zadáme adresu služby jako řetězec
- HttpClient(Uri baseAddress) – zadáme adresu služby jako Uri objekt
Já si ve své ukázkové aplikaci zvolím zlatou střední cestu a použiju adresu služby jako obyčejný řetězec. Instanci máme vytvořenou a ted musíme pouze poslat GET požadavek na danou adresu (Products) a služba by nám měla vrátit seznam všech produktů. Pro tento účel nabízí třída HttpClient metodu Get, která má opět několik variant, kde můžeme zadat např. parametry, ale já chci zavolat metodu, která nemá parametry tak použiju pouze variantu s jedním parametrem a to adresou (další varianty si ukážeme později)
// ujistete se ze vase adresa konci lomitkem !!!
using (HttpClient client = new HttpClient("http://localhost:8090/Eshop/"))
{
// poslu pozadavek GET na adresu http://localhost:8090/Eshop/Products
HttpResponseMessage response = client.Get("Products");
// vysledek vypisu do console
Console.WriteLine(response.Content);
}
Výsledkem po volání metody Get je objekt typu HttpResponseMessage, kde pouze pomocí vlastnosti vypíšu obsah odpovědi do console. Docela užitečnou vlastností je StatusCode, kde jsme schopni zjistit stav – 200 OK, atd.
Po spuštění naší klientské aplikace můžeme vidět následující odpověď
Tato odpověď není příliš dobrá a proto upravíme výpis do console. Na vlasntosti Content zavoláme metodu ReadAsString(), která nám převedě odpověď na smysluplnější řetězec. Další možností je zavolat metodu ReadAsStream(), která vrátí objekt Stream. Po úpravě může vypadat stejné volání následovně:
Už je to o něco lepší, ale pořád se v tom docela špatně čte, takže uděláme poslední úpravu. Přidáme using na System.Xml.Linq a místo metody ReadAsString() použijeme metodu ReadAsXElement(). Výsledek bude vypadat následovně:
Momentálně vypadá formát odpovědi docela pěkně na čtení, ale lépe než s XML souborem se nám bude asi pracovat přímo z daným objektem, v tomto případě s kolekcí ProductCollection. Pro tento případ budeme muset přidat referenci na System.Runtime.Serialization a samozřejmě na naší službu Eshop.Service. Upravíme tedy kód následovně:
// ujistete se ze vase adresa konci lomitkem !!!
using (HttpClient client = new HttpClient("http://localhost:8090/Eshop/"))
{
// poslu pozadavek GET na adresu http://localhost:8090/Eshop/Products
HttpResponseMessage response = client.Get("Products");
ProductCollection coll = response.Content.ReadAsDataContract<ProductCollection>();
// projdu kolekci a vypisu informace
foreach (Product p in coll)
{
Console.WriteLine("ID: {0}\tName: {1}", p.ID, p.Name);
}
}
A nyní vypadá výsledek takto:
Poznámka: Na začátku při vytváření mojí fake databáze jsem udělal menší chybičku, která by nám mohla vadit a to v případě, že provedeme update nějakého produktu tak při zavolání metody ReadAsDataContract<ProductCollection> dojde k zavolání bezparametrického konstrukturou, což nám vždycky vrátí defaultní hodnoty. Proto si ve třídy Product Collection vytvoříme další konstruktor s nějakým parametrem a načítání dat přesuneme do tohoto konstruktoru:
public ProductCollection(bool loadData)
{
if (loadData)
{
this.Add(new Product { ID = 1, Name = "Bunda", CategoryId = 1, Price = 3000 });
this.Add(new Product { ID = 2, Name = "Triko", CategoryId = 1, Price = 500 });
this.Add(new Product { ID = 3, Name = "Mikina", CategoryId = 1, Price = 200 });
this.Add(new Product { ID = 4, Name = "Kosile", CategoryId = 1, Price = 1500 });
this.Add(new Product { ID = 5, Name = "Parfem", CategoryId = 2, Price = 5000 });
this.Add(new Product { ID = 6, Name = "Hodinky", CategoryId = 2, Price = 6000 });
this.Add(new Product { ID = 7, Name = "Retizek", CategoryId = 2, Price = 200 });
}
}
První část máme za sebou a dále si vytvoříme na straně služby novou metodu UpdateProductPrice, která bude sloužit pro změnu ceny daného produktu. Jako parametry bude id produktu a nová cena. Jak si můžete všimnout o pár řádků níže služba je označená atributem WebInvoke, který nabízí kromě stejných vlastností jako WebGet i jednu vlastnost navíc nazvanou Method. Pomocí této vlastnosti můžeme specifikovat o jakou akci se bude jednat např. POST, PUT.
[Description("Aktualizuje cenu u produktu s danym id")]
[WebInvoke(UriTemplate = "/UpdateProductPrice/{id}?price={price}", Method = "PUT")]
public void UpdateProductPrice(string id, string price)
{
int parsedId;
decimal parsedPrice;
if (int.TryParse(id, out parsedId))
{
if (decimal.TryParse(price, out parsedPrice))
{
Product p = (from c in Products()
where c.ID == parsedId
select c).FirstOrDefault();
if (p != null)
p.Price = parsedPrice;
}
}
}
Co je v tomto kódu nového:
- Description – jedná se o atribut, kterým můžeme popsat naší metodu. Tento popis bude viditelný na stránce s nápovědou (/help)
- UriTemplate obsahuje následující adresu "/UpdateProductPrice/{id}?price={price}"
- UpdateProductPrice – název metody
- {id} – id produktu, nad kterým budeme chtít provést update
- za otazníkem následují další parametry. V našem případě price={price}"
Poznámka 1: Názvy ve složených závorkách musí odpovídat názvům parametrům v dané metodě. Např. použití price={price1}" vyvolá výjimku při spuštění služby, ale price123={price}" je naprosto v pohodě
Poznámka 2: Vstupní parametry musí být typu string či jiný objekt (nějaká naše třída), ale nelze použít int, decimal atd.
Na straně klienta vytvoříme příslušnou metodu, která provede update daného produktu:
private static void UpdateProductPrice_1()
{
using (HttpClient client = new HttpClient(URI))
{
// budu aktualizovat produkt s id = 1
string strUri = "UpdateProductPrice/1?price=19900";
HttpResponseMessage response = client.Put(strUri, HttpContent.CreateEmpty());
}
}
Jediné co zde udělám vytvoření instance HttpClienta, nastavení Uri adresy metody s příslušnými parametry a zavoláním metody Put, která má několik variant kde jedna z nich přijímá jako parametr adresu operace a HttpContent, který můžeme nastavit na Empty, jelikož vše potřebné nastavíme pomocí parametrů v adrese.
Pro otestování si zabalíme kód pro získání všech produktů do metody pojmenované GetProducts a upravíme v cyklu foreach kolekci coll tak, aby vzala pouze první prvek (aby jsme si nevypisovaly zbytečně všechny produkty) a do detailu o produktu přidáme informaci o ceně. Nejdříve tedy zavoláme metodu GetProducts, hned po ní Updateproduct_1, která změní původní cenu na novou a nakonec opět GetProducts, aby jsme si ověřili, zda opravdu došlo ke změně hodnoty:
static void Main(string[] args)
{
GetProducts();
UpdateProductPrice_1();
GetProducts();
}
Výsledek po volání vypadá následovně:
Další způsob aktualizace produktu bude takový, že jako parametr bude přímo objekt Product. Vytvoříme metody UpdateProduct, která bude velice podobná metodě UpdateProductPrice, akorát se bude lišit druhý parametr:
[Description("Aktualizuje produkt s danym id")]
[WebInvoke(UriTemplate = "/UpdateProduct/{id}", Method = "PUT")]
public void UpdateProduct(string id, Product product)
{
int parsedId;
if (int.TryParse(id, out parsedId))
{
Product p = (from c in Products()
where c.ID == parsedId
select c).FirstOrDefault();
if (p != null)
{
p.Name = product.Name;
p.CategoryId = product.CategoryId;
p.Price = product.Price;
}
}
}
A zavolání této metod z klienta provedu tak, že jako uri adresu nastavím UpdateProduct/1, vytvořím si novou instanci třídy Product a pomocí metody CreateDataContract třídy HttpContentExtensions vytvořím objekt typu HttpContent, který předám jako druhý parametr metody Put: Objekt product totiž nelze předat pomocí query stringu a tak musíme tento objekt zabalit a poslat na příslušnou adresu. Když se podíváme na stránku s nápovědou http://localhost:8090/Eshop/help/operations/UpdateProduct můžeme vidět ukázku jak má vypadat požadavek buď ve tvaru XML či JSON:
Kód na straně klienta:
private static void UpdateProduct_2()
{
using (HttpClient client = new HttpClient(URI))
{
// budu aktualizovat produkt s id = 1
string strUri = "UpdateProduct/1";
// vytvorim si novy produkt u ktereho provedu update
Product p = new Product
{
Name = "Novy produkt",
CategoryId = 2,
Price = 2500
};
HttpContent content = HttpContentExtensions.CreateDataContract<Product>(p);
HttpResponseMessage response = client.Put(strUri, content);
}
}
A aby jsme to mohly odzkoušet, ještě upravíme main metodu kde místo UpdateProductPrice_1() budeme volat naší nově vytvořenou metodu UpdateProduct_2()
Výsledek může vypadat takto:
Ještě než dnešní povídání zakončím, vytvoříme si nějakou metodu pro vytvoření:
[Description("Metoda pro vytvoreni noveho produktu")]
[WebInvoke(UriTemplate = "/CreateProduct", Method = "POST")]
public void CreateProduct(Product productToCretae)
{
// pokud neexistuje produkt s danym id, vloz ho do kolekce produktu
if (!FakeDatabase.Products.Any(p => p.ID == productToCretae.ID))
{
FakeDatabase.Products.Add(productToCretae);
}
}
No a klient můžeme velice jednoduše tuto metodu zavolat a vytvořit nový produkt: Kód vypadá téměř totožně jako metoda pro update
private static void CreateProduct()
{
using (HttpClient client = new HttpClient(URI))
{
// budu aktualizovat produkt s id = 1
string strUri = "CreateProduct";
Product p = new Product
{
ID = 22,
Name = "Nov ytvoreny produkt",
CategoryId = 2,
Price = 600
};
HttpResponseMessage response = client.Post(strUri, HttpContentExtensions.CreateDataContract<Product>(p));
}
}
Pro otestování jsem si opět upravil metodu GetProducts, kde si nyní z kolekce vyberu poslední prvek, abych zjistil zda se mi můj produkt opravdu vytvořil. Zde je výsledek: Nejdříve jsem si získal poslední produkt (ID=7), provedl vytvoření nového produktu a poté získal znovu poslední produkt, v tomto případě to už bude nově vytvořený produkt (ID=22)
Závěr:
Dnes jsme si ukázali jak vytvořit klienta a volat jednotlivé metody na straně služby, jak tuto odpověď zobrazit či deserializovat do konkrétního typu. Dále jak vytvořit metodu pro update, která přijímala buď jednotlivé parametry jako string hodnoty nebo parametry jako uživatelem definovaný typ např. třída Product. A v posledním případě zde byla ukázka jak vytvořit a volat metody pro vytváření nějakých objektů v našem případě vytvoření nového produktu. V příším díle bych rád ukázal jednotlivé možnosti formátování
- XML – XML = tenhle způsob komunikace jsme momentálně používal ve svých ukázkách
- XML – JSON – ukázka jak poslat request ve formátu XML a očekávat response ve formátu JSON
- JSON – XML – ukázka jak poslat request ve formátu JSON a očekávat response ve formátu XML
- JSON – JSON – ukázka jak poslat request a očekávat response ve formátu JSON
- jak přidat jednotlivým metodám parametr, podle kterého si uživatel zvolí v jakém formátu chce data očekávat
a další různé věci z této oblasti.