Postřehy k asp.net MVC
Na chvíli se přesunu od mých oblíbenějších témat k tématu, které se v poslední době často přetřásá, a tím je použití MVC při tvorbě asp.net aplikací. Jelikož vývojáři dostali před poměrně nedávnou dobou k dispozici implementaci tohoto vzoru pro .net framework od MS, a to v jeho první verzi, určitě neuškodí, pokud se na něj podívám z praktického hlediska.
MVC model vývoje jsem při vývoji webové aplikace, nebo spíše prezentace použil již před nějakou dobou, kdy jsem nasazoval svoji verzi blogu. Postupně jsem pak přešel na použití Preview verzí asp.net MVC a následně jsem Beta verzi použil při komerčním vývoji jedné aplikace.
Již v době Preview verzí jsem však maličko zápasil s tím, jakou úlohu hraje v tomto přístupu Controller, který tvoří onu aktivní část. A právě Controller se při větších aplikacích poměrně rozrůstá, což se mi přestalo po nějaké době líbit a tak jsem se snažil najít způsob, jak mu, takříkajíc, odlehčit.
Související otázky
Zároveň s tím jsem začal řešit i několik dalších navazujících otázek. Na některé z nich jsem prozatím nenalezl odpověď, jiné se mi povedlo vyřešit s tím, jak byly přidávány nové vlastnosti.
Jednoduchý systém pluginů
Mezi jedny z hlavních nedostatků tohoto přístupu vidím horší možnosti implementací pluginů. Neříkám, že je to nemožné, a existuje několik pokusů, např. SubControllery, nebo metoda RenderAction. Stále to však není tak elegantní, jak to je možné například v klasických webforms.
CRUD vs Content page
Jinou úlohou pak může být případ, kdy potřebujete jednoduchou obsluhu pro správu modelu, pokud je Controller poměrně jednoduchý a využívá jen jeden, případně několik málo servisních objektů poskytujících data - záměrně nemluvím o repository, která se mi v ukázkách, které jsou v hojné míře k vidění, nelíbí. Přeci jen je občas potřeba s předanými daty ještě něco málo udělat, transformovat je, a až následně uložit. Myslím si, že tuto práci by však neměl vykonávat Controller jako takový, ale právě nějaká servisní vrstva dříve, než se data dostanou k repository objektu – v takovém případě jde vše celkem hladce a jednoduše. K opačnému pólu složitosti a nabubřelosti se však dostávám v okamžiku, kdy je potřeba vytvořit maličko složitější Content page, která se skládá z více datových objektů, které poskytují různé servisní objekty.
Analogie s desktop aplikacemi
V průběhu vývoje jsem se tak stále více dostával k otázkám, zda by nešlo využít dosavadních znalostí z vývoje desktopových aplikací a vhodnou formou je použít při vývoji asp.net MVC aplikace.
Jelikož se v poslední době zabývám především vývojem WPF aplikací a zde se poměrně dobře ujal vzor MVVM (Model – View – ViewModel) pokusil jsem se jít touto cestou.
Malé odbočení na vzor MVVM
Jedná se o poměrně mladý návrhový vzor, který využívá výhod WPF a to zejména při bindingu. Spadá pak do stejné “škatulky” vzorů, které vycházejí z principu MVC, obdobně jako třeba vzor MVP (Model View Presenter). ViewModel pak, zjednodušeně řečeno, poskytuje jak data Modelu samotná, tak i připravuje další data, která jsou využívána View pro prezentaci. View je v takovém případě pasivní, čehož je zejména u WPF dobře skloubeno s reakcí na události vyvolávana v Modelu, případně právě ViewModelu.
Jak jsem již uvedl, mé rozhodnutí bylo učiněno v souvislosti s analogií na desktopové aplikace, kde je přeci jen bohatší objektový model, proto si určitě nemyslím, že mé řešení může být za všech okolností to pravé a správné – ani nechci tvrdit, že je to přesně MVVM implementace vzoru, přeci jen je zasazen do jiného modelu, ale ona analogie zde byla.
Řešení pomocí MVVM
Postupně jsem se přes problémy dostal k nástinu možného řešení. Netvrdím, že bude všespásné, mě se však v současné chvíli líbí. Alespoň co se týká použití v asp.net MVC, samotné asp.net nebo-li webforms neberu v tuto chvíli v potaz, neboť mají jiný přístup a mnoho věcí se zde dá řešit jiným způsobem.
Mým cílem bylo:
- odlehčit samotnému Controlleru
- zároveň jsem nechtěl řešit logiku aplikace ve View, což je v mnohých ukázkách poměrně časté. Jedná se především o případy, kdy se testuje zda hodnota v modelu je právě taková, potom vypiš toto a jinak tamto
- dalším požadavkem byla silná typovost ve View, to znamená žádné ViewData[klíč]
- … další podružné věci na které si momentálně nevzpomenu
Jak tedy mé řešení vypadá:
Každé View má přiřazenu svoji třídu ViewModel, která je instancována až v dané akci Controlleru. ViewModel si následně obhospodařuje práci se servisními objekty, které mu poskytují data - Model. Odlišností od původního návrhu pak je, že ViewModel nereaguje přes události na View, ale tyto reakce jsou mu vnuknuty od Controlleru voláním metod.
Tím se mi podařilo docela vhodně eliminovat množství kódu v samotné akci Controlleru. Zároveň je tento ViewModel předán jako model do View, čímž je zajištěna silná typovost tohoto View přes vlastnost Model. Uvnitř třídy ViewModel je potom řešena logika pro zobrazování – nikoliv zobrazování samotné. Je třeba zde ještě zmínit, že ViewModel může poskytovat jako svoji vlastnost přístup k podřízeným ViewModelům, které jsou použity při konstrukci View. Zejména se jedná o případy, kdy je třeba předat data modelu do UserControlu.
Tento první nástřel možného řešení byl v první fázy dostačující, samozřejmě přišla i jistá úskalí, ale o těch zase příště.
5 Comments
Petr Šnobelt said
Ahoj,
k těm pluginům, docela se mi líbí tohle řešení
http://blog.codeville.net/2008/10/14/partial-requests-in-aspnet-mvc/
Michal Augustýn said
Přidávám se k Petrovi - partial requests mi také zatím připadají jako nejelegantnější řešení widgets...
Michal Augustýn said
Jinak před pár dny jsem v jednom svém projektu všude přejmenoval Model na ViewModel, protože jsem si uvědomil, že Modelem nazývám něco, co Modelem není (ale defaultní struktura projektu k tomu naváděla). Nicméně Tvou ideu MVVM jsem nedotáhl do konce a "transformační logiku" jsem v duchu MVC nechal v controlleru a ModelViews používám jen jako data-holdery. Takže díky za nápad - určitě popřemýšlím o přesunu do ViewModels.
Ale měl bych pár otázek k Tvému řešení ;-)
1) Jak řešíš vstup dat (update modelu) ? Máš ViewModel přímo jako parametr action method nebo si ViewModel instancuješ sám a pak to řešíš ručně přes předaný FormCollection (nebo jiný value-provider) ? Nebo máš přímo MVVM připravený pro to, aby šel zupdatovat pomocí model-binderu (pak by šel použít i jako parametr action method) a nakonec jen zavoláš "zpracuj nabindovaný data" ?
2) Dejme tomu, že Model má properties Name, Surname, BirthDate a milión dalších. Jak tyhle properties zpropaguješ do ViewModel? Ručně přepsat mi přijde trošku pruda (možná nutná) a "class GenericViewModel<T> : T {}" asi taky nebude to pravé ořechové (i když pro účely zobrazování jednoho modelu asi dostatečné).
Díky! :)
Jarda Jirava said
Michal: řešení to samozřejmě má a dnes večer nebo zítra ráno by mělo vyjít pokračování tohoto příspěvku, ve kterém se budu snažit zodpovědět i další otázky a možné řešení. Jak jsem psal, nic není tak úplně jednoduché a pokud by měli i jiní zájem, rád bych se sešel i osobně a na toto téma diskutoval.
Radomir said
A nemel by jsi ukazku, ktera by MVVM vyuzivala?