Każdy(a) z Was pewnie spotkał się komunikatem "Aby wyświetlić tę stronę, Firefox musi ponownie przesłać dane etc..." O co chodzi i co zrobić, aby w naszych systemach tak nie było?
Podobny komunikat pokazuje, że odwiedzany przez nas serwis Web ma poważny problem, jakim jest ponowne wysyłanie POST. Schemat jest taki:
1. Przeglądarka wysyła polecenie GET i otrzymuje stronę z formularzem.
2. Użytkownik go wypełnia i klika "Wyślij".
3. Przeglądarka wysyła z użyciem POST dane do aplikacji.
4. Aplikacja odsyła stronę, że operacja się udała.
5. Użytkownik chce przeładować stronę z komunikatem.
6. Przeglądarka wysyła ostatnie zapytanie. Jakie? To z POST, z punktu 3.
7. Aplikacja drugi raz wykonuje akcję związaną z formularzem.
Czyli my chcemy zrobić coś normalnego (przeładować stronę, kliknąć "Wstecz"), a aplikacja zrobi coś więcej niż od niej oczekujemy. Hmmmm. A ciekawie robi się dopiero, kiedy ktoś używa przeglądarki, która nie ostrzega przed ponownym wysłaniem POST, takiej jak IE6.
Jak sobie z tym poradzić?
Z pomocą przychodzi wzorzec Post/Redirect/Get :) Zasada jest prosta - wynikiem POST nie może być strona, a przekierowanie do strony z GET:
1. Przeglądarka wysyła polecenie GET i otrzymuje stronę z formularzem.
2. Użytkownik go wypełnia i klika "Wyślij".
3. Przeglądarka wysyła z użyciem POST dane do aplikacji.
4. Aplikacja odsyła przekierowanie na stronę z wynikiem.
5. Przeglądarka pobiera ją używając GET.
6. Aplikacja odsyła stronę, że operacja się udała.
7. Użytkownik chce przeładować stronę z komunikatem.
8. Przeglądarka wysyła ostatnie zapytanie. Jakie? To z GET, z punktu 5.
9. Aplikacja ponownie wysyła stronę z wynikiem, a nie wykonuje ponownie akcji.
Dobra, tyle teoria. A jak wzorzec PRG zastosować w przykładowym frameworku opartym o wzorzec Model-Widok-Kontroler, takim jak ASP.NET MVC 1.0?
Jeżeli mamy akcję kontrolera reagującą na POST, to nie może ona zwracać widoku z wynikiem - musi przekierowywać do akcji GET, która to dopiero zwróci widok z wynikiem.
Mówiąc uniwersalnym językiem kodu, jeżeli mamy taki kod kontrolera (pomijam całą logikę, sprawdzanie poprawności itp.):
public ActionResult WypiszKsiazki()
{
return View(Repository.ListAllBooks());
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult DodajKsiazke(string title)
{
Repository.AddBook(title);
Repository.Submit();
ViewData["Message"] = "Dodanie zakończone sukcesem";
return View("WypiszKsiazki", Repository.ListAllBooks());
}
To właśnie tak nie powinno być :) OK, to robimy Redirect...
public ActionResult WypiszKsiazki()
{
return View(Repository.ListAllBooks());
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult DodajKsiazke(string title)
{
Repository.AddBook(title);
Repository.Submit();
ViewData["Message"] = "Dodanie zakończone sukcesem";
return RedirectToAction("WypiszKsiazki");
}
To nadal będzie źle - zaginie nam komunikat o sukcesie. Tutaj warto wykorzystać zamiast ViewData strukturę TempData: :)
public ActionResult WypiszKsiazki()
{
ViewData["Message"]= TempData["Message"];
return View(Repository.ListAllBooks());
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult DodajKsiazke(string title)
{
Repository.AddBook(title);
Repository.Submit();
TempData["Message"] = "Dodanie zakończone sukcesem";
return RedirectToAction("WypiszKsiazki");
}
Voila, gotowe! :) Tym niemniej ze względu na to, że trzeba dane "przepychać" przez TempData, to o Post/Redirect/Get najlepiej pomyśleć na samym początku :) A nie tak jak ja :P
Więcej o wzorcu PRG: http://en.wikipedia.org/wiki/Post/Redirect/Get
sobota, 15 maja 2010
Subskrybuj:
Komentarze do posta (Atom)
Fajne! Tylko masz małego buga na ostatniej pozycji drugiej listy. ;)
OdpowiedzUsuńPoprawiony :) W sumie miałem jeszcze 6 minut luzu :D
OdpowiedzUsuńPaweł z Głuchołaz pisze:
OdpowiedzUsuńDlaczego nikt nie zauważył że zrobiłem w Site.Master ...ViewData["Message"] ?? TempData["Message"]...?
W ten sposób nie trzeba robić tego okropnego przepisywania z TempData do ViewData a widok załatwia sprawę sam :)
Ha, jak sprytnie :) Np. ja nie zauważyłem, bo site.master nie czytuję na bieżąco :)
OdpowiedzUsuń