Na letošním pražském MS Festu jsem měl v Altairově přednášce krátký výstup o mém vlastním ASP.NET frameworku, který jsme s Tomášem Jechou nazvali Redwood. Protože se mě celkem dost lidí ptalo, co tím sleduju a proč nepřejdeme na MVC jako každý slušný .NET vývojář, rozhodl jsem se to sepsat do článku.
WebForms ještě nejsou mrtvé, ale…
Microsoft v novém “vNext stacku” s WebForms nepočítá a chce, aby všichni přešli na MVC anebo na nějaký čistě klientský javascriptový framework a ASP.NET Web API. Neznamená to sice, že WebForms v dohledné době přestanou fungovat; myslím, že WebFormové aplikace budeme potkávat minimálně ještě 10 let, ale Microsoft do nich už nehodlá moc investovat.
V praxi to znamená, že to ještě pár let budou držet třetí strany, které prodávají svoje balíky komponent (např. Telerik, DevExpress apod.), nicméně platforma jako taková se rozvíjet nebude a zrovna WebForms mají uvnitř řadu omezení, která zmenožňují udělat v nich všechno. Navíc WebFormy obsahují spoustu old-fashioned věcí z dob .NET 1.0. I ty slavné nové silně typové bindingy jsou tam spíš doplácané a mají spoustu omezení, mnoho komponent ani nepoužívá generiku, přestože by se to hodilo (např. kolekce Controls u libovolné komponenty není silně typová atd.). Při dnešních aplikacích to dost komplikuje práci a vývoj přestává být pohodlný a rychlý.
Proč nechci MVC
Na toto téma jsem napsal už několik článků a na mém pohledu se v zásadě nic nemění – jako zásadní problém v něm vidím, že kvůli absenci viewstate neumí a nikdy nebude umět snadno udělat plnohodnotné vlastní komponenty, jako to uměly WebForms – prostě komponenty, které by si nějak někde držely vlastní stav, mohly být na jedné stránce umístěny třeba 5x a obsahovaly by vlastní netriviální logiku, která s tím stavem nějak pracuje. Ne, že by se to v MVC nedalo napsat, ale je to dost komplikované.
V aplikacích, které dnes a denně píšu, je stavovost prostě potřeba a framework, který si persistenci stavu řeší sám, má velmi významné plus – šetří to totiž obrovské množství kódu. Kromě toho se mi v MVC nelíbí views, protože HTML helpery se už používají pro úplně každou kravinu a celý ten kód je míchání netriviálních bloků C# kódu a HTML – prostě klasický spaghetti kód. Pokud bych chtěl nějaké UI vygenerovat dynamicky, tak si to taky musím psát sám.
Jak to celé vlastně vzniklo
Před 2 lety jsem si začal hrát s klientskými MVVM frameworky, zejména s Angularem a Knockoutem. Zejména Knockout se mi zalíbil, je krásně minimalistický, řeší sice jen jeden problém, ale řeší ho velmi dobře. V kombinaci s dalšími javascriptovými knihovnami (Kendo UI pro pořádné webové komponenty, Durandal pro implementaci SPA) a ASP.NET Web API jsme v tom napsali pár aplikací a celkový dojem je dobrý, i zákazníkům se to líbilo – aplikace vypadá rychleji, nemusí dělat plné postbacky, při prvním načtení se to stahuje trochu déle, protože JS knihoven je skoro 1MB (z čehož asi 900kB je Kendo UI), ale pak si aplikace se serverem vyměňuje už jen pár JSONů, které mají jednotky kB, což je krásné a rychlé a velmi dobře se to hodí pro informační systémy a podobné aplikace.
Každopádně je zde několik problémů:
- Celé to řešení je konglomerát asi 20 JS knihoven a .NET backendu, naučit se a zvládnout to všechno není vůbec jednoduché.
- 99% javascriptového kódu nedělá nic zásadního, jen nějak reprezentuje nebo transformuje data ze serveru.
- Pokud bych takto chtěl napsat třeba eshop, narazím, protože to vyhledávače nezaindexují. Musím tedy míchat dohromady serverové a klientské renderování.
A když jsem na loňském MVP Summitu viděl, že to s WebForms do budoucna nevypadá dobře, napadlo mě, že bych těch 99% otravného javascriptu mohl nějak vygenerovat, protože to bylo pořád na jedno brdo a každý viewmodel jsme dělali podle jednoho zaběhnutého schématu.
Pak jsem zkusil něco napsat, párkrát jsme si na to sedli s Tomem Jechou, napsali jsme kus kódu, pak jsme zjistili, že neumím pořádně vysvětlit, jak konkrétně si to představuju a že bude nejlepší, když napíšu nějaký prototyp a uvidíme, jestli to k něčemu je nebo ne. Do toho do mě začal hudrat Altair, že jestli to nebude umět klasické plnohodnotné postbacky, tak to bude na hovno, že na mizerném připojení je klasický postback nejlepší, načež jsem se naštval a během posledního MVP Summitu jsem prototyp během 3 dnů napsal a plácnul na http://github.com/riganti/redwood. Plné postbacky to neumí, zato to umí část toho, co jsem chtěl.
Jak to funguje a co to umí
Základem je viewmodel, který je napsán v C#. Je to třída, která má vlastnosti a funkce. Viewmodel pro jednoduchou kalkulačku by vypadal asi takhle:
public class CalculatorViewModel
{
public int Number1 { get; set; }
public int Number2 { get; set; }
public int Result { get; set; }
public void Evaluate()
{
Result = Number1 + Number2;
}
}
ViewModel drží kompletní stav stránky a pokud neobsahuje žádné kočičárny, tak lze snadno serializovat. Ve view pak Redwood podporuje vlastní komponenty, které budou moct mít svůj ekvivalent ControlState z WebForms a budou si ho samy přidávat do viewmodelu (nebude v něm potřeba nic deklarovat). A samozřejmě je možné dělat binding do ViewModelu, takže to vypadá třeba takhle:
<rw:TextBox Text="{value: Number1}" />
No a když přijde HTTP request na tuhle stránku, tak Redwood vytvoří strom komponent, vytvoří viewmodel, zavolá postupně funkce Init, Load a PreRender, jako to bylo ve WebForms, a nakonec vyrenderuje výstup. Bindingy přitom převede na Knockoutové data-bind. Při renderování výstupu přidá do stránky odkaz na knihovnu Redwood.js, která obsahuje pár krátkých infrastrukturních funkcí a ještě se do stránky přidá serializovaný viewmodel (to je vlastně viewstate z WebForms).
V okamžiku, kdy člověk klikne na tlačítko, tak se viewmodel vezme, serializuje a pošle AJAXem na klienta (postback), kde framework nějak pozná, kterou funkci má zavolat a s jakými parametry (commandy ve viewmodelu mohou mít libovolné množství parametrů). Na serveru se to zpracuje a nový viewmodel se pošle na klienta.
<rw:Button Click="{command: Evaluate()}" />
A abych vyřešil problém s klientským a serverovým renderováním, tak pokud použiju třeba Repeater, tak mu jde říct, jestli tu ItemTemplate má renderovat na serveru, nebo až na klientovi. Akorát zatím Redwood neumí ekvivalent UpdatePanelu, takže co se při prvním requestu vygeneruje, to už tam zůstane navěky. Asi tam ani UpdatePanel dělat nechci, spíš jen nějakou attached property Render.UpdateOnClient=”true”, která se přidá kamkoliv a nějak se o to postará.
Abych pravdu řekl, líbí se mi to
Řeší to různé neduhy WebForms:
- Nevím, co mám ve viewstate. V Redwoodu vím, protože viewstate je můj viewmodel, který mám pod kontrolou.
- Viewmodely jsou na rozdíl od codebehindu testovatelné – nejsou tu závislosti na komponenty.
- Je to modernější, v bindingu bude plně typová IntelliSense.
Řeší to i ten problém z MVC – jak nepřehledné views plné dlouhých fragmentů C# kódu, tak i ty stavové komponenty jakmile je tam dopíšu.
Má to jednotnou syntaxi pro klientské a serverové šablony a nemusíte psát javascript, pokud nechcete.
Co je v plánu
Aktuální stav je takový, že je to v praxi skoro nepoužitelné – chybí tam dost zásadní věci. Zde mám takový krásný seznam, co tam chci v nejbližší době doplnit.
- Plně stavové komponenty a Master Pages – aktuálně nejsou naimplementované
- Dokončit základní sadu komponent
Chybí FileUpload, který se bude muset řešit nějak pitomě přes iframe, protože AJAXem soubory nejde posílat
Dál to chce aspoň GridView a DataPager
A pak bych rád nějaké wrappery nad bootstrapem, abychom se zbavili toho CSS class hell
- Upravit serializaci viewmodelů – aby šlo říct, že tuhle property chci při postbacku vynechat, tuhle chci zašifrovat a tuhle nechci šifrovat, ale chci ji digitálně podepsat (aby ji klient viděl, ale nemohl ji sám změnit).
- Integrace do Visual Studia – mám rozepsaný plugin, který řeší zvýrazňování syntaxe a IntelliSense
- Validace – chci ji tam přidat nějak neintruzivně, využít na to asi attached properties, které podporujeme, a pochopitelně navázat na Data Annotations v .NETu
- Lokalizace – ta jediná snad nebude tak těžká, přidáme speciální binding {localize: ResourceFile.ResourceKey}.
- Dál bych chtěl přes Roslyn překládat jednoduché metody ve viewmodelu do javascriptu, aby se nemusel dělat postback i kvůli akcím, které lze na viewmodelu provést lokálně.
Jak vidíte, práce je na tom dost, ale potenciál vidím obrovský. Serverová část běží na OWINu, nemá závislosti na System.Web, takže bude kompatibilní s novým stackem. Neobsahuje nic nativního, takže teoreticky by přes to šly psát i multiplatformní mobilní a desktopové aplikace – UI bude v HTML a backend v C#. V kombinaci s Apache Cordova by to mohlo dostat další rozměr.
P.S. A děkuji panu Stanislavu Lukešovi za první (a užitečný) pull request.