Znovupoužiteľné .NET knižnice pomocou Portable Library Tools

Portable Library Tools sú nástroje pre Visual Studio, ktoré umožňujú vytvoriť jednu .NET knižnicu ktorá bude podporovať viacero platforiem: .NET 4 Framework, Silverlight 4, Windows Phone 7 a XNA hry. Doteraz bolo potrebné mať jeden spoločný projekt so zdrojovými súbormi a pre každú cieľovú platformu vytvoriť zvlášť projekt a do neho pomocou linkovania pridať zdrojáky zo zdieľaného projektu. Takto bolo možné vyriešiť portovanie zdieľaného kódu existujúcej Silverlight 4 aplikácie do Phone 7 aplikácie. Lenže toto riešenie má veľké problémy so spravovaním súborov – všetky súbory trebalo ručne synchronizovať, jeden súbor sa dá vo Visual Studio otvoriť iba jeden krát, ďalší pokus o otvorenie skončí chybovou hláškou, ktorú treba odklikávať a tak podobne. Hľadanie správnych API, ktoré fungujú na všetkých potrebných platformách, tiež nebolo jednoduché. Veľmi ľahko sa programátor mohol odviazať a použiť niečo, čo zrazu v inom projekte spôsobilo kompilačnú chybu, alebo závislosť na inej DLLke (niektoré triedy z .NET 4 sú v Silverlighte 4 v inej fyzickej DLL a tak istom na Phone 7 sa nachádzajú v inej DLL). Portable Library Tools tak prinášajú obrovské zjednodušenie písania knižnice, ktorú plánujeme použiť v aplikáciách pre rôzne platformy.

Pri vývoji Windows Phone 7 aplikácie, ktorá bude používať FogBugz API, som najprv mal vytvorený Phone 7 projekt, do ktorého som prepisoval moju existujúcu implementáciu klienta pre FogBugz API. Tento klient bol obrovský, napísaný cez WCF a so synchrónnymi volaniami. Pre Phone 7 bolo potrebné kompletne prepísať veľkú čast kódu, pretože tak ako Silverlight, aj Phone 7 API obsahuje iba asynchrónne volania pri prácii s I/O a sieťou. Mohol som sa zároveň zbaviť závislosti na WCF a kód poriadne zoštíhliť. Keď som mal Phone 7 knižnicu hotovú, tak vyšla finálna verzia Portable Library Tools nástrojov a rozhodol som sa, že projekt zmením tak, aby som ho v budúcnosti mohol použiť kdekoľvek. To so sebou prinieslo niekoľko zmien v kóde, ktoré nie sú na prvý pohľad jasné a vyžadovali si trochu googlenia.

Boolean.ToString()

Jedna pomocná metóda používala volanie Boolean.ToString(CultureInfo.InvariantCulture);, ktoré nie je v PLT podporované. InvariantCulture som použil z „best-practices“ dôvodov. Boolean hodnotu som formátoval do query stringu a POST dát, takže som použil InvariantCulture, ktorá zabezpečí, že primitívny dátový typ bude naformátovaný do takéto textového formátu, ktorý bude bez problémov spracovateľný inou aplikáciou. PTL však podporuje iba Boolean.ToString() metódu. Podľa dokumentácie pre Boolean preťaženie metódy, ktoré som ja volal, úplne ignoruje parameter IFormatProvider a teda je jednoduché opraviť volanie na jednoduché ToString() bez zmeny chovania kódu.

PS: Pôvodná idea za použitím CultureInfo.In­variantCulture bola, aby som vždy dostal iba „true“ alebo „false“ stringy. Lokalizované .NETy dokážu vracať texty ako „Pravda“ a „Nepravda“. A samozrejme FogBugz server nedokáže spracovať takýto lokalizovaný text. Nemám však momentálne dôkaz, že Boolean.ToString() naozaj vráti lokalizovaný text v niektorých špeciálnych prípadoch a MSDN dokumentácia v iných jazykoch hovorí o „true“ a „false“ stringoch. Predpokladám teda, že o tento potencionálny bug sa vôbec nemusím starať, lebo neexistuje 🙂

Uri.EscapeUriS­tring(string) a Uri.EscapeDataS­tring(string)

Ako webový vývojár som zvyknutý používať triedu HttpUtility na zakódovanie textu do správne formátu, ak má byť text súčasťou URL adresy, HTML obsahu alebo atribútu. Táto trieda je však z knižnice System.Web.dll ktorá je dosť veľká a nie je vhodná ani pre desktopové aplikácie, a už vôbec nie pre Silverlight či Windows Phone. HttpUtility trieda je v týchto dvoch platformách ale prítomná v System.Window­s.Browser.dll knižnici. V Portable Library Tools ju však nenájdeme a potrebujeme náhradu. Existujú však nové metódy v triede Uri, ktoré boli pridané v .NET 3.5 SP1, ktoré rátajú s tým, že vývojári často pracujú s HTTP protokolom aj mimo webových aplikácií. Máme teda k dispozícii dve nové metódy: Uri.EscapeUriString(string) a Uri.EscapeDataString(string) ktoré sú súčasťou základnej systémovej knižnice a umožnia nám správne kódovať URL/URI adresy, query stringy a POST dáta.

Uri.EscapeUriString(string) metóda slúži pre prácu so stringami, ktoré budú súčasťou URL/URI adresy – napr. cesta k súboru na serveri a query string dáta.

-    fullServiceUrl.Query = "cmd=" + HttpUtility.UrlEncode(command);
+    fullServiceUrl.Query = "cmd=" + Uri.EscapeUriString(command);

Uri.EscapeDataString(string) metóda zase zakóduje správne stringy, ktoré budú súčasťou POST údajov.

StreamWriter writer = new StreamWriter(webRequest.EndGetRequestStream(asyncResult);
-    string name = HttpUtility.UrlEncode(param.Key);
-    string value = HttpUtility.UrlEncode(param.Value);
+    string name = Uri.EscapeDataString(param.Key);
+    string value = Uri.EscapeDataString(param.Value);
writer.Write(name + "="+ value + "&");

Synchronizati­onContext

Všetky API pre prácu s I/O sú v Silverlighte a na Windows Phone výhradne asynchrónne. Treba si preto uvedomiť, že kód aplikácia si vyžiada údaje zo serveru a tieto údaje dojdú späť asynchrónne – v inom threade. Ak teda používateľ klikne na tlačidlo „Načítaj údaje so serveru“ a handler metóda zavolá WebClient.Dow­nloadStringAsyn­c(), tak metóda okmažite skončí a grafické rozhranie aplikácie nezamrzne, lebo nečaká, kým sa stiahnu údaje zo serveru. Namiesto toho treba počkať, kým sa nezavolá ďalší handler, ktorý už dostane stiahnuté údaje. Toto sa však deje v samostatnom vlastné, ktorý je iný od UI vlákna. Načítané údaje teda môžu byť spracované a grafické rozhranie aplikácie stále nemusí byť blokované. Až po spracovaní môžeme aktualizovať UI – napr. zobraziť stiahnutý text. Toto však nemôžeme urobiť kedykoľvek. UI sa vykresluje vo svojom vlastnom vlákne a preto musíme vyslať požiadavku o zmenu UI pomocou triedy Dispatcher. Dispatcher je špeciálna trieda, ktorá vie, ako sa vykresľujú WPF, Silverlight a Phone 7 aplikácie a nie je dostupná v Portable Library Tools. Pretože PLT knižnica môže bežať v rôznych prostrediach, treba použiť namiesto špeciálneho Dispatcheru všeobecnú triedu, ktorá zabezpečí správnu synchronizáciu kódu, ktorý potrebujeme vykonať. Takouto triedou je SynchronizationContext s dvoma metódami: Post() a Send(). Ide o asynchrónny a synchrónny variant spustenia kódu v správnom vlákne, ktoré môže upravovať aj grafické rozhranie. PLT knižnica získa aktuálny synchronizačný kontext pomocou vlastnosti SynchronizationContext.Current a podľa toho, či je aplikácia napísaná pre Win Forms, WPF, Silverlight či Phone 7, tak sa použije správny synchronizačný kontext na vykonanie kódu.

Portable Library Tools sú zaujímavá vec, pokiaľ potrebujete vyvinúť aplikáciu pre rôzne .NET platformy a použiť v nich spoločný zdieľaný kód. Samozrejme treba mať namysli, že PLT sprístupnia v projekte iba tie API, ktoré sú dostupné na všetkých platformách, pre ktoré je knižnica vytvárané a z počiatku to môže byť dosť obmedzujúce. Mnohé veci sa však dajú napísať v .NETe veľmi univerzálne, avšak treba použiť správnu sadu API. Toto bol hlavne prípad odstránenia HttpUtility, na ktorú som ako webový vývojár veľmi zvyknutý. .NET sa vyvíja a niekedy je ťažké udržať krok so všetkými novými a lepšími API. Na druhej strane táto evolúcia .NETu so sebou prináša aj duplicitu API a treba sa vedieť správne zorientovať, ktoré triedy a metódy použiť v danom projekte.