Wednesday, February 17, 2010 1:28 PM
lukaashek
WCF 4 – WebHttp Services – Jak na výjimky
Když jsme vytvářely WCF služby komunikující klasicky pomocí SOAP zpráv, tak jsem využívaly FaultException pro odeslání chybové zprávy zpět klientovi. WebHttp Services nabízí podobnou funkcionalitu, akorát se používá třída WebFaultException, která dědí právě z FaultException. WebFaultException nabízí dvě varianty, klasickou a generickou.
Když se podíváme na sluřbu EshopService, tak vidíme, že jsme si minule vytvořily metodu pro aktualizaci ceny nazvanou UpdateProductPrice. Právě na této metodě si ukážeme první použití zpracování výjimek. Metoda přijímá dva parametry: id a price, kde oba tyto parametry musí být čísla. Přidáme tedy první podmínku, pokud se nepovede přetypovat řetězec na číslo, ukončíme metodu vyvoláním příslušné výjimky. Při vytváření WebFaultException nám stačí když uvedeme text chybové zprávy a příslušný HttpStatusCode (existuje celá řada těchto statusů např. NotFound, BadRequest, ServiceUnavailable, apod.). Upravená metoda tedy vypadá následovně (popis v komentářích)
[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;
// nepodari-li se prevest id na cislo vyhod vyjimky
if (!int.TryParse(id, out parsedId))
{
throw new WebFaultException<string>("parameter id must be a number", System.Net.HttpStatusCode.BadRequest);
}
// nepodari-li se prevest price na cislo vyhod vyjimku
if (!decimal.TryParse(price, out parsedPrice))
{
throw new WebFaultException<string>("parameter price must be a number", System.Net.HttpStatusCode.BadRequest);
}
// najdu produkt
Product p = (from c in FakeDatabase.Products
where c.ID == parsedId
select c).FirstOrDefault();
// pokud neexistuje, vyhod vyjimku
if (p == null)
throw new WebFaultException<string>(string.Format("Product with id={0} does not exist", parsedId),
System.Net.HttpStatusCode.NotFound);
else
p.Price = parsedPrice;
}
A na straně klienta pouze upravíme již existující metodu UpdateProductPrice_1, kde jsme přidaly 3 parametry: id, price a format aby jsme si ukázaly jak vypadají výjimky odeslané buď pomocí XML či JSON. Kromě přidání parametrů si ještě vypíšeme StatusCode.
private static void UpdateProductPrice_1(string id, string price, string format)
{
Console.WriteLine("ID: " + id);
Console.WriteLine("Price: " + price);
Console.WriteLine("Format: " + format);
Console.WriteLine("\n\n");
using (HttpClient client = new HttpClient(URI))
{
// budu aktualizovat produkt s id = 1
string strUri = "UpdateProductPrice/" + id + "?price=" + price;
// pridame hlavicku s informaci o formatu odpovedi
using (HttpRequestMessage request = new HttpRequestMessage("PUT", strUri))
{
request.Headers.Accept.AddString(format);
request.Content = HttpContent.CreateEmpty();
// odeslu pozadavek
HttpResponseMessage response = client.Send(request);
// zobrazim status a vysledek
Console.WriteLine("Status: " + response.StatusCode);
Console.WriteLine("Response: " + response.Content.ReadAsString());
Console.WriteLine("\n\n");
}
}
}
Pro otestování zavoláme metodu UpdateProductPrice_1 hned několikrát:
// bad request: id
UpdateProductPrice_1("str", null, "application/json");
UpdateProductPrice_1("str", null, "application/xml");
// bad request: price
UpdateProductPrice_1("-1",null, "application/json");
UpdateProductPrice_1("-1", null, "application/xml");
// not found
UpdateProductPrice_1("0", "234", "application/json");
UpdateProductPrice_1("0", "234", "application/xml");
// ok
UpdateProductPrice_1("1", "234", "application/json");
UpdateProductPrice_1("1", "234", "application/xml");
A výsledek bude vypadat následovně:
============================================================
ID: str
Price: NULL
Format: application/json
Status: BadRequest
Response: "parameter id must be a number"
============================================================
ID: str
Price: NULL
Format: application/xml
Status: BadRequest
Response: <;string xmlns="http://schemas.microsoft.com/2003/10/Serialization/">parameter id must be a number</string>
============================================================
ID: -1
Price: NULL
Format: application/json
Status: BadRequest
Response: "parameter price must be a number"
============================================================
ID: -1
Price: NULL
Format: application/xml
Status: BadRequest
Response: <;string xmlns="http://schemas.microsoft.com/2003/10/Serialization/">parameter price must be a number</string>
============================================================
ID: 0
Price: 234
Format: application/json
Status: NotFound
Response: "Product with id=0 does not exist"
============================================================
ID: 0
Price: 234
Format: application/xml
Status: NotFound
Response: <;string xmlns="http://schemas.microsoft.com/2003/10/Serialization/">Product with id=0 does not exist</string>
============================================================
ID: 1
Price: 234
Format: application/json
Status: OK
Response:
============================================================
ID: 1
Price: 234
Format: application/xml
Status: OK
Response:
Vylepšení:
Místo vyhození výjimky s pouhým textovým popisem si vytvoříme třídu, která bude obsahovat více informací o vzniklém problému. Vytvoříme si tedy třídu NotFoundException, která bude obsahovat následující vlastnosti Code, Message a Description (tyto vlastnosti jsou pouze pro demonstraci použití):
public class NotFoundException
{
public int Code { get; set; }
public string Message { get; set; }
public string Description { get; set; }
}
Když si tedy uživatel zažádá o produkt s ID, které neexistuje, vrátíme mu trošku více informací proč ten produkt s ID třeba neexistuje
// pokud neexistuje, vyhod vyjimku
if (p == null)
{
NotFoundException detail = new NotFoundException
{
Code = 404,
Message = "Product not found",
Description = string.Format("There is no product with id={0}. Change ID and try again", parsedId)
};
throw new WebFaultException<NotFoundException>(detail, System.Net.HttpStatusCode.NotFound);
}
A na straně klienta jsme si vytvořily pomocnou metodu, která vezme objekt HttpResponseMessage a format a je-li StatusCode roven NotFound, provedeme deserializaci v závislosti na uvedeném formátu. Deserializací získáme objekt NotFoundException a budeme si moct přečíst doplňující informace
private static void GetExceptionDetail(HttpResponseMessage response, string format)
{
// abych mohl vicekrat cist obsah nahraju ho do bufferu
response.Content.LoadIntoBuffer();
// zobrazim StatusCode
Console.WriteLine("Status: " + response.StatusCode);
if (response.StatusCode == System.Net.HttpStatusCode.NotFound)
{
NotFoundException detail = format == "application/xml"
? response.Content.ReadAsDataContract<NotFoundException>()
: response.Content.ReadAsJsonDataContract<NotFoundException>();
if (detail != null)
{
Console.WriteLine(" Code: {0}", detail.Code);
Console.WriteLine(" Message: {0}", detail.Message);
Console.WriteLine(" Description: {0}", detail.Description);
}
}
}
Po opětovném zavolání metod UpdateProductPrice_1 se výsledek změní pouze u té se StatusCode = NotFound následovně
============================================================
ID: 0
Price: 234
Format: application/json
Status: NotFound
Code: 404
Message: Product not found
Description: There is no product with id=0. Change ID and try again
Response: {"Code":404,"Description":"There is no product with id=0. Change ID and try again","Message":"Product not found"}
============================================================
ID: 0
Price: 234
Format: application/xml
Status: NotFound
Code: 404
Message: Product not found
Description: There is no product with id=0. Change ID and try again
Response: <;NotFoundException xmlns="http://schemas.datacontract.org/2004/07/Eshop.Service" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"><Code>404</Code><Description>There is no product with id=0. Change ID and try again</Description><Message>Product not found</Message></NotFoundException>
============================================================
Závěr:
V dnešním krátkém příspěvku jsme si ukázaly jak můžeme na straně služby vyvolat výjimku a na straně klienta si přečíst informace o výjimce. Kromě jednoduchých textových výjimek jsme si ukázaly možnost, jak vytvořit vlastní třídy s vlastnostmi obsahující mnohem více informací o výjimce a jak tuto třídu poté deserializovat na straně klienta ke zjištění oněch dodatečných informací. Jak vidíte, práce s výjimkami je zde velice jednoduchá. V příštím článku se nejspíše podíváme na možnosti cacheování.