ASP.NET MVC – PRG

V tomto článku bych se rád podíval na to, jak je možné v ASP.NET MVC implementovat vzor Post-Redirect-Get. Ale proč se vůbec snažit o implementaci tohoto vzoru? Akademická odpověď by byla, že POST (stejně jako PUT a DELETE) HTTP metody jsou určeny pouze pro práci nad daty, nikoliv na prezentaci těchto dat, takže bychom měli vždy po POST, PUT a DELETE redirectovat na stránku, která prezentuje relevantní data. Praktické důvody, proč se následování tohoto vzoru v praxi vyplatí, jsou minimálně dva. Prvním je to, že uživatel v ideálním případě nikdy v adresním řádku prohlížeče neuvidí adresu, jejíž requestování by způsobilo operaci s daty. A protože ji neuvidí, nemůže si ji uložit do bookmarků ani poslat kamarádovi. Druhou výhodou tohoto nezobrazení adresy je zabránění vícenásobnému odeslání dat. Znáte to, stránka se načítá nějak pomalu, tak dáte F5, prohlížeč se zeptá, jestli odeslat data znovu, OK, a najednou jsem odeslal dva stejné posty do fóra, zaplatil dvakrát účet za telefon nebo koupil dvě auta ;-) Tak přesně tohle se nemůže nikdy stát, když budete ctít vzor PRG.

Co to pro nás znamená v ASP.NET MVC? Když máme nějakou action method, kterou máme odekorovánu patřičnými atributy tak, aby byla volatelná pouze pomocí POST (příp. PUT nebo DELETE), tak z ní musíme vždy redirectovat. Tuto „chybu“ je možné vidět i tehdy, pokud si ve Visual Studiu necháme vytvořit nový controller:

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(int id, FormCollection collection)
{
    try
    {
        // TODO: Add update logic here
        return RedirectToAction("Index");
    }
    catch
    {
        return View();
    }
} 

Jak je vidět, po úspěšné validaci dojde správně k přesměrování, ale při nějaké chybě (většinou špatné zadání dat uživatelem) dojde k znovuzobrazení formuláře. Při tomto znovuzobrazení se využívá jedna fičura ASP.NET MVC, o které málokdo ví.

Pokud renderujeme inputí kontrolky pomocí standardních Html.TextBox, Html.TextArea apod., tak všechny tyto metody se VŽDY nejprve podívají, zda neexistuje v ModelState záznam s klíčem shodným s jménem právě renderované kontrolky. Pokud ano, tak se použije. To znamená, že i když explicitně zadáme value v nějakém overloadu předchozích metod, tak se tato value vůbec nepoužije pokud existuje hodnota v ModelState.

Jak se ale tyto hodnoty do ModelState dostanou? Odpověď je více než jednoduchá – při model-bindigu. Což v drtivé většině případů znamená pokud má action method nějaké parametry nebo pokud zavoláme metodu (Try)UpdateModel. Model-binder totiž do ModelState neukládá pouze chyby (které se pak testují pomocí ModelState.IsValid a renderují pomocí Html.ValidationSummary apod.), ale informace o všech provedených bindováních.

Zpět k vygenerovanému „chybnému“ kódu. Jednoduše uděláme to, že místo „return View();“ přesměrujeme na iniciální stránku pro editaci (což je ve většině případů stejný redirect jako v případě úspěšné editace). Zde ale narážíme na problém – ModelState nepřežije redirect, takže uživatel nevidí své špatné hodnoty, ale vidí iniciální hodnoty. Jak z toho ven? Inu musíme zajistit, aby ModelState přežil redirect, čehož se dá velice elegantně dosáhnout pomocí action filterů.

Uděláme si action filter, který se spustí po provedení akce, který si zjistí, jestli redirectujeme, a pokud ano, tak uloží aktuální ModelState do TempData (tam nám data přežijí do dalšího requestu – vnitřně používá session). Dále si pak uděláme druhý action filter (příp. to naprasíme všechno do jednoho), který se v případě renderingu stránky pokusí vyzvednou ModelState z TempData.

Když máme hotové tyto dva action filtery, tak je stačí aplikovat na náš bázový controller a od té doby můžeme v klidu redirectovat z POSTů při zachování ModelState.

Komu by se nechtělo výše popsané action filtery implementovat, tak může mrknout do tohoto článku. Implementace to je moc pěkná, snad bych jen při ukládání ModelState do TempData navíc ještě zkontroloval, zda byl daný request učiněn jako POST, DELETE nebo PUT. Pokud ne, nemá většinou smysl ModelState ukládat.

K výše uvedenému bych ještě dodal, že situace se nám trošku komplikuje v případě Ajax requestů. Tam si prohlížeče s redirectem povětšinou neporadí, takže si to řeší každý po svém nějakým workaroundem. Na druhou stranu Ajax requesty nejsou přímo viditelné pro uživatele a tudíž nehrozí problémy popsané v úvodu článku, takže v případě Ajax requestů bych se na PRG s prominutím vyprdnul ;-)

Příspěvek byl publikován v rubrice Programování a jeho autorem je Augi. Můžete si jeho odkaz uložit mezi své oblíbené záložky nebo ho sdílet s přáteli.

Napsat komentář

Vaše emailová adresa nebude zveřejněna.

Můžete používat následující HTML značky a atributy: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>