ASP.NET MVC – Template Method pattern pro akce

Jednou z hlavních výhod, o kterých se mluví v souvislosti s MVC webovými frameworky, je to, že svým designem vedou vývojáře ke správnému rozvrstvení aplikace. Nejen začínající vývojáři ale občas umisťují něco tam, kam to nepatří, např. dávají moc logiky do views nebo (a to je asi nejtypičtější chyba) dávají aplikační logiku do controllerů. Podívejme se na to, jak je možné dále vést vývojáře ke správnému designu aplikace v rámci MVC frameworku, konkrétně ASP.NET MVC.

Model představuje business logiku aplikace a pro každý typ projektu je jeho podoba specifická a samotný vzor MVC se jím moc nezabývá. Pokud máme model jako samostatné assemblies, které nereferencují assemblies s prezentační vrstvou (nejčastěji vlastní ASP.NET MVC aplikaci), tak máme dobře nakročenu k vyhnutí se tomu, abychom měli v business vrstvě logiku specifickou pro nějakou prezentační vrstvu. A to je dobře.

View by měl obsahovat ideálně žádnou logiku, maximálně repeatery apod. Pokud máme kodera, kterého nejsme sto uhlídat, nezbývá než zvolit view-engine, který využívá nějaký deklarativní jazyk, čímž přímo znemožňuje drátování složitější logiky do views.

Největší problém se skrývá v controllerech. Jak jsem psal výše, typickou chybou je strkat do něj nějakou business logiku. Ale jak přinutit programátora, aby v controlleru dělal jen to, co by měl? Pomocí návrhového vzoru Template Method! Tedy vytvořit vzorovou action method a umožnit do ní programátorovi vstoupit jen v přesně určených okamžicích.

Vzor Template Method se nejčastěji implementuje pomocí virtuálních metod. Vytvářet ale pro každou action method nějakou třídu a přetěžovat její virtuální metody, to by byl docela overkill. Ale máme i jinou možnost, jak vzor Template Method implementovat:  jednoduše vytvoříme metodu, která bude brát jako parametry delegáty, které bude naše metoda ve vhodných okamžicích volat.

A jak by měla taková ideální action method vlastně vypadat? Podle mého skromného názoru nějak takto:

  1. inicializace view-modelu
  2. informování view-modelu o uživatelských akcích
  3. vrácení odpovídajícího action result

Samozřejmě v každém z těchto kroků může nastat nějaký problém, na který je třeba také nějak zareagovat.

Vytvořil jsem proto extension methods pro typ System.Web.Mvc.ControllerBase, který implementuje vzor Template Method pro ideální action method. Její hlavička vypadá takto:

public static ActionResult Process<TViewModel>(this ControllerBase controller,
Func<TViewModel> initializeViewModel,
Func<TViewModel, Exception, ActionResult> handleInitializeViewModelException,
Func<TViewModel, ActionResult> handleInvalidViewModelAfterInitialization,
Action<TViewModel> updateViewModel,
Func<TViewModel, Exception, ActionResult> handleUpdateViewModelException,
Func<TViewModel, ActionResult> handleInvalidViewModelAfterUpdate,
Func<TViewModel, ActionResult> obtainActionResult,
Func<TViewModel, Exception, ActionResult> handleObtainActionResultException); 

Opravdu zde tedy máme jen tři hlavní výkonné delegáty, které odpovídají třem uvedeným krokům + delegáty na ošetření chyb. Asi nemá cenu chodit okolo horké kaše a hned ukáži, jak se tato extension method používá:

public class ProductController : Controller
{
    public ActionResult Add()
    {
        return this.ProcessGET<ProductViewModel>(
            null, // blank product is always created successfully
            (ViewModel) => View("Product"));
    }

    [AcceptVerbs(HttpVerbs.Post)]
    public ActionResult Add(FormCollection form)
    {
        return this.Process<ProductViewModel>(
            null, // blank product is always created successfully
            (viewModel) => { if (TryUpdateModel<Product>(viewModel.Product)) viewModel.AddNew(); },
            (viewModel) => RedirectToAction("Add"), // some error occured
            (viewModel) => RedirectToAction("Edit", new { id = viewModel.Product.Id })); // product successfully created
    }

    public ActionResult Edit(int id)
    {
        return this.ProcessGET<ProductViewModel>(
            () => new ProductViewModel(id),
            (viewModel) => RedirectToAction("Index"), // probably product with specified id doesn't exist
            (ViewModel) => View("Product"));
    }

    [AcceptVerbs(HttpVerbs.Post)]
    public ActionResult Edit(int id, FormCollection form)
    {
        return this.Process<ProductViewModel>(
            () => new ProductViewModel(id),
            (viewModel) => RedirectToAction("Index"), // probably product with specified id doesn't exist
            (viewModel) => { if (TryUpdateModel<Product>(viewModel.Product)) viewModel.SaveChanges(); },
            (viewModel) => RedirectToAction("Edit", new { id = viewModel.Product.Id }), // some error occured
            (viewModel) => RedirectToAction("Edit", new { id = viewModel.Product.Id })); // all is ok
    }
}

Jak je vidět, v GET metodách, které slouží jen k zobrazení hodnot, voláme metodu ProcessGET, která neumožňuje specifikovat delegáty pro informování view-modelu.

Pomocí mých extension methods tedy lze vést programátora k tomu, aby do controlleru dal jen nezbytně důležité věci, což je v mém přístupu k ASP.NET MVC jen komunikace s view-modelem.

Použití této metody trošku nepřehledně, protože na první pohled není vidět, kdy je jaká lambda použita. Pokud ale v C# 4.0 použijeme pojmenované parametry metod při volání, situace bude mnohem přehlednější…

Soubor s extension methods je možné stáhnout zde.

9 thoughts on “ASP.NET MVC – Template Method pattern pro akce

  1. No mně se zatím třeba moc nelíbí, protože ten zápis je takovej nepřehlednej 🙂 Ale v mým případě třeba zase lepší, než mít ve všech action methods téměř stejnej kód…

    To se mi líbí

  2. Čitelnost takového kódu, je hodně špatná, možná by jí prospělo, nadefinovat lambda výrazy jako lokální proměnné.

    Podle mě, je lepším prostředkem k donucení programátora, aby tam necpal, co nemá, prosté code review. 🙂

    To se mi líbí

  3. Buď ty lokální proměnné, komentáře nebo v C# 4.0 ty pojmenované parametry. Když si na to člověk udělá snipetty, tak to pak může sekat jako Baťa cvičky 🙂

    To se mi líbí

  4. Urobit okolo toho trosku toho fluent interface rozhrania [http://blog.vyvojar.cz/vlko/archive/2007/09/28/fluentinterface.aspx] a hned by to vyzeralo lepsie, takto to je proste brrr:)

    To se mi líbí

  5. Z fluent interface se mi ježí i chlupy na zádech, takže do toho bych já osobně nešel 🙂 Ale moc se mi líbí nápad v jednom z komentářů odkazovaného článku – využít object initializery…

    To se mi líbí

  6. Ahoj, tak když jsem dočetl článek tak jsem přesně na fluent interface pomyslel taktéž. Myslím, že by to mohlo být poměrně elegantní řešení. Navíc by mohl být kaskádovitý, tj. omezovat dostupné metody a rázem by to bylo přehledné i pro zápis. Určitě dobrý nápad.
    — J.

    To se mi líbí

  7. Zapis moc prehledny neni, ale myslenka se mi moc libi. Vytvaret pro vsechny action metody tridu s implementaci metod by byl overkill, ale muze se napsat jeden pomocny adapter, ktery prijima delegaty a tyto delegaty spousti pri volani metod, ktere predstavuji kroky „sablonove“ metody.

    EDIT: Vidim, ze uz je druhy dil, kde asi neco podobneho resite.

    To se mi líbí

  8. Ano ano, přesně ten pomocný adapter mám v tom druhém dílu. Pak jsem ho navíc použil ještě pro fluent interface, což myslím syntaxi ještě více zpřehlednilo a navíc se v IntelliSense nabízí jen to, co je logické zavolat, takže je komfortnější i psaní.

    To se mi líbí

Zanechat odpověď

Vyplňte detaily níže nebo klikněte na ikonu pro přihlášení:

Logo WordPress.com

Komentujete pomocí vašeho WordPress.com účtu. Odhlásit /  Změnit )

Google photo

Komentujete pomocí vašeho Google účtu. Odhlásit /  Změnit )

Twitter picture

Komentujete pomocí vašeho Twitter účtu. Odhlásit /  Změnit )

Facebook photo

Komentujete pomocí vašeho Facebook účtu. Odhlásit /  Změnit )

Připojování k %s

Tento web používá Akismet na redukci spamu. Zjistěte více o tom, jak jsou data z komentářů zpracovávána.