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.

  • Facebook
  • TwitThis
  • LinkedIn
  • Live
  • Google Bookmarks
  • email