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ě.
Comments
Petr Šnobelt
Ahoj, k tem pluginum, docela se mi líbí tohle rešení http://blog.codeville.net/2008/10/14/partial-requests-in-aspnet-mvc/
Michal Augustýn
Pridávám se k Petrovi - partial requests mi také zatím pripadají jako nejelegantnejší rešení widgets...
Michal Augustýn
Jinak pred pár dny jsem v jednom svém projektu všude prejmenoval Model na ViewModel, protože jsem si uvedomil, že Modelem nazývám neco, co Modelem není (ale defaultní struktura projektu k tomu navádela). Nicméne Tvou ideu MVVM jsem nedotáhl do konce a "transformacní logiku" jsem v duchu MVC nechal v controlleru a ModelViews používám jen jako data-holdery. Takže díky za nápad - urcite popremýšlím o presunu do ViewModels. Ale mel bych pár otázek k Tvému rešení ;-) 1) Jak rešíš vstup dat (update modelu) ? Máš ViewModel prímo jako parametr action method nebo si ViewModel instancuješ sám a pak to rešíš rucne pres predaný FormCollection (nebo jiný value-provider) ? Nebo máš prímo MVVM pripravený 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? Rucne prepsat mi prijde trošku pruda (možná nutná) a "class GenericViewModel<T> : T {}" asi taky nebude to pravé orechové (i když pro úcely zobrazování jednoho modelu asi dostatecné).
Díky! :)
Jarda Jirava
Michal: rešení to samozrejme má a dnes vecer nebo zítra ráno by melo vyjít pokracování tohoto príspevku, ve kterém se budu snažit zodpovedet i další otázky a možné rešení. Jak jsem psal, nic není tak úplne jednoduché a pokud by meli i jiní zájem, rád bych se sešel i osobne a na toto téma diskutoval.
Radomir
A nemel by jsi ukazku, ktera by MVVM vyuzivala?