V minulé části jsem popsal ty hezké fíčury, které technologie ASP.NET Dynamic Data přináší – generování UI podle metadat a databázového schématu, možnost specifikace třídění, filtrů atd. Nyní si ukážeme, na co se musíte připravit, pokud tuto technologii chcete reálně nasadit a použít.
Málo vestavěných filtrů
Dynamic Data obsahují v základu pouze filtry pro enum, boolean hodnoty a pro cizí klíče. Krásně tedy můžete vyfiltrovat produkty, které jsou přiřazeny v kategorii Beverages nebo Condiments (správně jste poznali Northwind). Bohužel všechny tyto filtry jsou reprezentovány pomocí ComboBoxu.
Chcete-li něco pokročilejšího, například enum, ale chcete v něm vybírat více voleb zároveň (dej mi přijaté a aktuálně zpracovávané objednávky), musíte si to napsat sami. Chcete prohledávat v názvech? Napište si to. Chcete filtrovat boolean sloupec pomocí CheckBoxu? Chcete zvolit období nebo filtrovat na konkrétní datum? Nic takového tam není, přestože většina webových aplikací tyto problémy potřebuje řešit. Navíc texty pro “null kategorii” jsou v komponentách zadrátované natvrdo, což se mi také hrubě nelíbí.
Vzhledem k tomu, že filtrovací komponenty se dodávají ve formě složky plné ASCX souborů, můžete v nich sice snadno dělat úpravy a vytvářet další, ale pokud děláte na více projektech (a takových z nás je pohříchu většina), budete tyto složky muset synchronizovat, což celou věc značně komplikuje. Kdyby aspoň šla smečka ASCX komponent zabalit do DLL knihovny, ale ona nejde.
Kecám, jde, počkejte si, až vyjde můj seriál (záznam z přednášky) Vývoj komponent v ASP.NET WebForms na webu www.mstv.cz a podívejte se, jak blbě se to musí řešit a ani potom to nefunguje pořádně. I když u Dynamic Data to fungovat zrovna bude i bez chyb kompilace, komponenty z té složky se vytváří dynamicky a nereferencují se ve stránkách přímo. Ale i když to jde, není to úplně elegantní řešení. Proč tohle prostě není v ASP.NET nebo ve Visual Studiu zaintegrováno? To je fakt tak nerozumný požadavek?
Stránkovací komponenta
Další nedomyšleností ASP.NET Dynamic Data je komponenta GridViewPager, která se dodává. Základní myšlenkou je, že ji umístíte do PagerTemplate komponenty GridView a ona si ve správnou chvíli najde rodičovský GridView, z nějž si vytáhne číslo stránky, případně mu jej nastaví.
Implementovaná je velmi ledabyle, rodičovský GridView si hledá natvrdo ve fázi OnLoad (tyhle věci se ve zbytku .NETu typicky řeší lazy-loadingem, totiž že se komponenta najde až při prvním použití; není to úplně špatně, ale je to jinak, což už špatně je). Navíc pokud GridView nenajde, ani nevyhodí výjimku, takže pokud tuto komponentu neobratně upravíte, dostanete po čumáku NullReferenceException výjimkou a není jasné, kde se vzala. Alespoň kontrola a vyhození něčeho normálního by neškodilo, ale autoři byli líní. Nejhorší je, že tohle je kód od Microsoftu a vývojáři ho vidí. A spousta z nich se tím i bude inspirovat.
Dalším problémem je, že GridViewPager již nefunguje s komponentou ListView ani DataList. Dobře, DataList nikdo nepoužívá, ale ListView používají naopak skoro všichni a stránkování musí řešit přes DataPager, což je sice průchodné, ale každá z těchto komponent je jiná a nejsou zaměnitelné. Pokud některé přehledy v aplikaci budu dělat přes GridView a jiné přes ListView, typicky chci, aby stránkování vypadalo stejně. A zase si to musím psát sám.
Atributy jsou, ale nic nedělají
Spoustu čachrů sice v metadatech jde udělat, ale Dynamic Data mnohé z nich nepodporují. Můžete například nastavit DataType na EmailAddress nebo Url, ale Dynamic Data se na to vykašlou a neudělají nic. Vestavěná FieldTemplate s validátory na to není, takže si ji buď musíte napsat, anebo upravit šablonu Text_Edit. Podobně to platí s některými dalšími vymoženostmi, takže pokud nasadíte Dynamic Data a divíte se, že nefunguje, ujistěte se, že to testujete na nějakém jednoduchém atributu, který je opravdu implementován.
Odporná implementace většiny komponent
Když jsem se na Dynamic Data díval do referenčních zdrojáků, udělalo se mi místy velmi nevolno a měl jsem sto chutí někoho zastřelit. Pokud čtete můj Twitter, víte, o co jde. Pokud se chcete dívat, jak se píšou komponenty, nastudujte si Repeater (mimochodem velmi užitečné a poučné, takhle se to má dělat), ale nedívejte se do Dynamic Data!
Prvním a asi nejokatějším prohřeškem je fakt, že Dynamic Data podporuje Linq To SQL a Entity Framework. V budoucnu asi těžko bude podporovat cokoliv jiného, protože na určitých místech byli autoři líní dodělávat patřičná rozhraní, aby mohli pracovat s datovými zdroji obou technologií unifikovaně, takže se kód hemží konstrukcemi typu if (context is LinqDataSource) blabla; else if (context is EntityDataSource) blabla;. A že by aspoň něco vyhodili, pokud to omylem použijete na jiný DataSource, to by halt museli zmáčknout víc čudlíků a napsat tam ten další else, takže nic.
Já jen pevně doufám, že Microsoft neplánuje další OR-Mappery. Aby se ty podmínky už moc nerozrostly.
Dalším prohřeškem, který budete proklínat, pokud stránky skládáte dynamicky a nemáte komponenty natvrdo (dost často je to potřeba dělat). Vězte, že spousta inicializací se provádí ve fázi Page.InitComplete a nezměníte to. Pokud tedy jakoukoliv komponentu, která používá DynamicData nenaplníte a kompletně nenastavíte už ve fázi OnInit, nebude fungovat korektně. Protože ona si sice iniciativně zaregistruje handler na Page.InitComplete, ale ta se už nezavolá, když jste ve fázi Load. Přitom databinding proti datovým zdrojům se pouští až po PreRender, tak proč je nutné vědět, co a jak bindujeme, takhle brzo? Je kvůli tomu nemožné parametrizovat například tabulku, kterou zobrazujete, něčím, co je jako hodnota komponenty, v OnInit totiž není načtený ViewState ani stav formulářových komponent. Jedině že byste hrabali do Request.Form, ale to slušní programátoři dělají jen v případech krajní nouze a skřípou při tom zuby, zbyly-li jim jaké.
Pokud tedy z libovolného důvodu potřebujete inicializace a zapínání Dynamic Data provádět později než v OnInit, připravte se na to, že občas budete muset zavolat internal metodu pomocí Reflection, protože vývojáři QueryableFilterRepeateru například šetřili virtuálními metodami, takže vám nepomůže ani dědičnost. A protože je tak komponenta tak začunítkovaná, je podobné pravdě, že to nebude zpětně kompatibilní, protože její vnitřní implementaci změní. Doufám, že k lepšímu.
Když už jsem u toho QueryableFilterbazmeku, to je učebnicový příklad naprosto minimální implementace, která dělá požadovanou věc (sice jen tak tak, ale jo). Žádná header ani footer template, žádná EmptyDataTemplate, žádná kolekce Items, prostě nic. Takovouhle komponentu občas napíšu já, když potřebuji něco udělat, nechci to naprasit, ale víc toho nepotřebuju a je to specifické pro konkrétní projekt. Třída, která je oficiální součástí .NET Frameworku, by mohla být při vší účtě napsána pořádně. A nejhorší je, že tyhle nedodělky by daly sotva na pár hodin práce. To na to Microsoft nemá kapacity nebo snad prostředky?
A takových situací potkáte jistě mnohem více, možná se taky budete vztekat, proč FormView napojený na Dynamic Data sice před insertem či updatem spustí validátory a odchytí validační výjimky, které z modelu vyhazujete, databázovou operaci neprovede, ale validátory z neznámého důvodu schová, takže v ItemInserting a ItemInserted musíte ručně volat Page.Validate, aby to fungovalo.
Tak co teda?
Pro ilustraci výše uvedený výčet myslím stačí. Protože tento post odhaluje poměrně závažné komplikace, mohlo by se zdát, že Dynamic Data není radno používat. Opak je ale pravdou, je to úžasná technologie, která je ovšem šitá tak trochu horkou jehlou a abyste ji mohli použít, musíte se v ASP.NET vyznat a kus si dodělat. Zvládnete ji bez problémů použít “odshora”, tedy že si necháte vygenerovat celou aplikaci, upravíte PageTemplates a hodíte to designérovi, ať na to udělá CSSka. Tento scénář testovali, byl koneckonců už v .NETu 3.5. Dá se použít i “odspoda” (novinka v .NET 4.0), ale tady si už musíte trochu hrát a bez referenčních zdrojáků či Reflectoru se obejdete dost těžko.
Přesto si myslím, že když do Dynamic Data člověk dopíše pár tříd, které výše zmiňované problémy řeší, doplní si chybějící komponenty a zapamatuje si pár imbecilit, které tam jsou, je to technologie, která je velmi použitelná a usnadní spoustu práce. Má to svá ale, na druhou stranu pokud to vyřešíte v jednom projektu, pak už to stačí jen použít jinde. A synchronizovat nejnovější verze úprav mezi všemi projekty, k čemuž vám dopomáhej libovolný bůh nebo něco jiného, v co věříte.
Další fází evoluce vývojáře k ideálnímu a nedosažitelnému stavu jménem RAD je fakt, kdy zjistíte, že psát metadatové třídy je taky pruda (skoro stejná jako psát ty formuláře), a že je chcete generovat. Skončíte tím, že si napíšete aplikaci nebo T4 šablonu, která vygeneruje metadatové třídy pro Linq To Sql či Entity Framework. A protože přeci jen někdo při vývoji Dynamic Data přemýšlel hlavou, můžete místo natvrdo zadrátovaných názvů sloupců a chybových hlášek tato data uložit do Resources a vygenerovat aplikací při té příležitosti i ty RESX soubory spolu s metadatovými třídami. Aspoň že se to dá lokalizovat.
Takhle teprve vypadá způsob vývoje webových aplikací, který mě už ani skoro neštve. Ještě aby zmizelo HTML, CSS a Javascript.