ASP.NET MVC – Template Method pattern pro akce podruhé

V předchozím příspěvku jsem ukázal, jak je možné pomocí návrhového vzoru Template Method zajistit správnou strukturu action method. Mé řešení sice funguje, ale na první pohled je kód dost nepřehledný – člověk vůbec nemá představu, k čemu ta která lambda slouží. Jak z toho ven?

První řešení, které je dostupné snad ve všech normálních programovacích jazycích, jsou komentáře. Ale psaní pořád stejných komentářů je pruda, takže bychom si mohli třeba vytvořit snippet, který by nám generoval volání metody Process včetně komentářů.

Další řešení navrhoval Aleš – použít lokální proměnné. Zde je přehlednost asi tak stejná jako u předchozího řešení a také musíme psát nějaký ten kód navíc.

Další řešení, které mě napadlo, využívá nové fičury jazyka C# 4.0 – pojmenované a volitelné parametry. Jednoduše před každou lambdu napíšeme jméno parametru s dvojtečkou, příp. pokud daný parametr vůbec nechceme použít, tak ho prostě vůbec neuvedeme. Přehlednost je perfektní, nemusíme psát tolik kódu navíc (implementace je dokonce jednodušší) a vůbec mi toto řešení připadá nejméně obstruktivní.

Ale protože C# 4.0 je teprve na cestě, chtělo by to nějaké řešení, které bude simulovat volitelné parametry v C# 3.0. K tomu lze využít např. object initializery. Takže nakonec to dopadlo tak, že jsem vytvořil třídu, která ma jako properties delegáty (stejné jako měla původní extension metoda) + má metodu Run, která spustí provádění action method. Použití mé třídy ActionTemplate pak vypadá takto:

public class ProductController : Controller
{
    public ActionResult Add()
    {
        return new ActionTemplateWithInstantiableViewModel<ProductViewModel>(this, true)
        {
            ObtainActionResult = (viewModel) => View("Product"),
        }.Run();
    }

    [AcceptVerbs(HttpVerbs.Post)]
    public ActionResult Add(FormCollection form)
    {
        return new ActionTemplateWithInstantiableViewModel<ProductViewModel>(this, true)
        {
            UpdateViewModel = (viewModel) =>                   { if (TryUpdateModel<Product>(viewModel.Product)) viewModel.AddNew(); },
            HandleInvalidViewModelAfterUpdate = (viewModel) => RedirectToAction("Add"),
            ObtainActionResult = (viewModel) =>                RedirectToAction("Edit", new { id = viewModel.Product.Id })
        }.Run();
    }

    public ActionResult Edit(int id)
    {
        return new ActionTemplate<ProductViewModel>(this, true)
        {
            InitializeViewModel = () =>         new ProductViewModel(id),
            ObtainActionResult = (viewModel) => View("Product")
        }.Run();
    }

    [AcceptVerbs(HttpVerbs.Post)]
    public ActionResult Edit(int id, FormCollection form)
    {
        return new ActionTemplate<ProductViewModel>(this, true)
        {
            InitializeViewModel = () =>                                new ProductViewModel(id),
            HandleInvalidViewModelAfterInitialization = (viewModel) => RedirectToAction("Index"),
            UpdateViewModel = (viewModel) =>                           { if (TryUpdateModel<Product>(viewModel.Product)) viewModel.SaveChanges(); },
            HandleInvalidViewModelAfterUpdate = (viewModel) =>         RedirectToAction("Edit", new { id = viewModel.Product.Id }),
            ObtainActionResult = (viewModel) =>                        RedirectToAction("Edit", new { id = viewModel.Product.Id }),
        }.Run();
    }
} 

Implementaci si můžete stáhnout zde.

No a nakonec máme to nejlepší. Sice jsem se tomu bránil, ale nakonec jsem se odhodlal naprogramovat výše uvedené pomocí fluent interface. Sice to byla fůra psaní, ale syntaxe je přehledná a je tu i dobrý komfort při psaní. Ukázka použití zde:

public class ProductController : Controller
{
    public ActionResult Add()
    {
        return this.ViewModel<ProductViewModel>().
            PropagateExceptionsToModelState().
            ActionResult((viewModel) => View("Product"));
    }

    [AcceptVerbs(HttpVerbs.Post)]
    public ActionResult Add(FormCollection form)
    {
        return this.ViewModel<ProductViewModel>().
            PropagateExceptionsToModelState().
            Update((viewModel) =>                 { if (TryUpdateModel<Product>(viewModel.Product)) viewModel.AddNew(); }).
            HandleInvalidViewModel((viewModel) => RedirectToAction("Add")).
            ActionResult((viewModel) =>           RedirectToAction("Edit", new { id = viewModel.Product.Id }));
    }

    public ActionResult Edit(int id)
    {
        return this.ViewModel<ProductViewModel>(() => new ProductViewModel(id)).
            PropagateExceptionsToModelState().
            ActionResult((viewModel) =>               View("Product"));
    }

    [AcceptVerbs(HttpVerbs.Post)]
    public ActionResult Edit(int id, FormCollection form)
    {
        return this.ViewModel<ProductViewModel>(() => new ProductViewModel(id)).
            PropagateExceptionsToModelState().
            HandleInvalidViewModel((viewModel) =>     RedirectToAction("Index")).
            Update((viewModel) =>                     { if (TryUpdateModel<Product>(viewModel.Product)) viewModel.SaveChanges(); }).
            HandleInvalidViewModel((viewModel) =>     RedirectToAction("Edit", new { id = viewModel.Product.Id })).
            ActionResult((viewModel) =>               RedirectToAction("Edit", new { id = viewModel.Product.Id }));
    }
} 

A implementace je ke stažení zde.

3 thoughts on “ASP.NET MVC – Template Method pattern pro akce podruhé

  1. Sleduju, jak se porad peres s tim, ze se ti to uplne nelibi :))
    Ale rozhodne zajimave reseni problemu, taky se obcas chytim, ze se Zend Frameworkem v MVC delam v Controlleru leccos, ale v PHP bohuzel podobna reseni nemaji sanci :))
    Uz mam nejvyssi cas, abych zacal psat tu MVC aplikaci, co jsem si ve skole naordinoval, takze se zajmem hltam spoty! Jen tak dal! 🙂

    To se mi líbí

  2. Osobně se mi spíš líbí fluent. Jen mimochodem – zatím většina lidí, co jsem viděl, nerada zarovnává výrazy pod sebe (viz. např. ten fluent a kusy kódu po => )

    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 )

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.