7. Wdrożenie technologii Ajax w aplikacji ASP.NET MVC
7.1. Rola AJAX w aplikacji internetowej
Na razie analizowane przykłady szkoleniowe mają następującą architekturę:
![]() |
Aby przejść z widoku [Vue1] do widoku [Vue2], przeglądarka:
- wysyła żądanie do aplikacji internetowej;
- odbiera widok [Vue2] i wyświetla go zamiast widoku [Vue1].
Jest to klasyczny schemat:
- żądanie z przeglądarki;
- wygenerowanie widoku przez serwer WWW w odpowiedzi na żądanie klienta;
- wyświetlenie tego nowego widoku przez przeglądarkę.
Istnieje jeszcze jeden sposób interakcji między przeglądarką a serwerem internetowym: AJAX (Asynchronous JavaScript and XML). W rzeczywistości chodzi o interakcje między widokiem wyświetlanym przez przeglądarkę a serwerem internetowym. Przeglądarka nadal wykonuje to, co potrafi, czyli wyświetla widok HTML, ale jest teraz sterowana przez kod JavaScript wbudowany w wyświetlany widok HTML. Schemat wygląda następująco:
![]() |
- w [1] na stronie wyświetlanej w przeglądarce ma miejsce zdarzenie (kliknięcie przycisku, zmiana tekstu itp.). Zdarzenie to jest przechwytywane przez kod JavaScript (JS) osadzony na stronie;
- w momencie [2] kod JavaScript wysyła żądanie HTTP, tak jak zrobiłaby to przeglądarka. Żądanie ma charakter asynchroniczny: użytkownik może nadal korzystać ze strony bez blokowania jej działania w oczekiwaniu na odpowiedź na żądanie HTTP. Żądanie przechodzi przez standardowy proces przetwarzania. Nic (lub prawie nic) nie odróżnia go od zwykłego żądania;
- w przypadku [3] do klienta wysyłana jest odpowiedź JS. Zamiast pełnego widoku HTML wysyłany jest raczej widok częściowy HTML, strumień XML lub JSON (notacja obiektowa JavaScript);
- w [4] skrypt JavaScript pobiera tę odpowiedź i wykorzystuje ją do aktualizacji określonego obszaru wyświetlanej strony HTML.
Z punktu widzenia użytkownika następuje zmiana widoku, ponieważ to, co widzi, uległo zmianie. Nie dochodzi jednak do całkowitego przeładowania strony, a jedynie do częściowej modyfikacji wyświetlanej strony. Przyczynia się to do płynności działania i interaktywności strony: ponieważ nie następuje całkowite odświeżenie strony, można sobie pozwolić na obsługę zdarzeń, których wcześniej nie obsługiwano. Na przykład można zaproponować użytkownikowi listę opcji w miarę wpisywania przez niego znaków w polu wprowadzania danych. Przy każdym nowym wpisanym znaku wysyłane jest żądanie AJAX do serwera, który następnie zwraca kolejne propozycje. Bez Ajaxa tego rodzaju pomoc przy wpisywaniu była wcześniej niemożliwa. Nie można było przeładowywać nowej strony przy każdym wpisanym znaku.
7.2. Podstawy JQuery i JavaScript
Często dołączaliśmy bibliotekę JavaScript JQuery do naszych stron. Znajduje się w niej następujący wiersz:
<script type="text/javascript" src="~/Scripts/jquery-1.8.2.min.js"></script>
Uwaga: należy dostosować wersję jQuery do wersji programu Visual Studio.
Technologia Ajax biblioteki ASP.NET MVC wykorzystuje bibliotekę JQuery. Sami napiszemy kilka skryptów JQuery. Dlatego też przedstawimy teraz podstawowe informacje o JQuery, które należy znać, aby zrozumieć skrypty z tego rozdziału.
Tworzymy nowy projekt [Exemple-04] w ramach naszego rozwiązania [Exemples]:
![]() |
Aby korzystać z Ajaxu w ASP.NET i MVC, w pliku konfiguracyjnym [Web.config] i [1] musi znajdować się następujący wiersz:
<appSettings>
...
<add key="UnobtrusiveJavaScriptEnabled" value="true" />
</appSettings>
Wiersz 3 zezwala na korzystanie z Ajaxu w widokach ASP.NET. Jest on domyślnie obecny.
Tworzymy plik HTML [JQuery-01.html] w folderze [Content] nowego projektu [2]:
![]() |
Plik ten będzie miał następującą zawartość:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>JQuery-01</title>
<script type="text/javascript" src="/Scripts/jquery-1.8.2.min.js"></script>
</head>
<body>
<h3>Rudiments de JQuery</h3>
<div id="element1">
Elément 1
</div>
</body>
</html>
- wiersz 6: import pliku JQuery (dostosuj wersję do swojej wersji programu Visual Studio);
- wiersze 10–12: element strony o identyfikatorze [element1]. Będziemy pracować z tym elementem.
Wyświetlamy ten plik w przeglądarce Google Chrome: [4] i [5]:
![]() |
W przeglądarce Google Chrome należy wpisać [Ctrl-Maj-I], aby wyświetlić narzędzia programistyczne [6]. Zakładka [Console] [7] umożliwia uruchamianie kodu JavaScript. Poniżej podajemy polecenia JavaScript, które należy wpisać, wraz z ich objaśnieniami.
JS | wynik |
|
: zwraca zbiór wszystkich elementów o identyfikatorze [element1], a więc zazwyczaj zbiór zawierający 0 lub 1 element, ponieważ na jednej stronie nie mogą występować dwa identyczne identyfikatory HTML. | ![]() |
|
: stosuje tekst [blabla] do wszystkich elementów kolekcji. Powoduje to zmianę treści wyświetlanej na stronie | ![]() |
|
ukrywa elementy kolekcji. Tekst [blabla] nie jest już wyświetlany. | ![]() |
|
: ponownie wyświetla kolekcję. Dzięki temu widzimy, że element o identyfikatorze [element1] posiada atrybut CSS style='display: none;', który powoduje, że element jest ukryty. | |
|
: wyświetla elementy kolekcji. Tekst [blabla] pojawia się ponownie. To atrybut CSS style='display: block;' zapewnia to wyświetlenie. | ![]() |
|
: nadaje atrybut wszystkim elementom kolekcji. Atrybutem jest tutaj [style], a jego wartością [color: red]. Tekst [blabla] zmienia kolor na czerwony. | ![]() |
![]() | |
![]() |
Warto zauważyć, że identyfikator URL przeglądarki nie uległ zmianie podczas wszystkich tych operacji. Nie doszło do żadnej komunikacji z serwerem WWW. Wszystko odbywa się wewnątrz przeglądarki. Teraz przyjrzyjmy się kodowi źródłowemu strony:
![]() |
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>JQuery-01</title>
<script type="text/javascript" src="/Scripts/jquery-1.8.2.min.js"></script>
</head>
<body>
<h3>Rudiments de JQuery</h3>
<div id="element1">
Elément 1
</div>
</body>
</html>
To jest tekst początkowy. Nie odzwierciedla on w żaden sposób zmian, które wprowadziliśmy w elemencie w wierszach 10–12. Należy o tym pamiętać podczas debugowania kodu JavaScript. W takich przypadkach przeglądanie kodu źródłowego wyświetlanej strony jest często bezcelowe. Aby poznać kod źródłowy aktualnie wyświetlanej strony, należy postępować w następujący sposób:
![]() |
Wiemy już wystarczająco dużo, aby zrozumieć skrypty JS, które pojawią się poniżej.
7.3. Aktualizacja strony za pomocą strumienia HTML
7.3.1. Widoki
Zamierzamy przeanalizować następującą aplikację:
![]() |
- w [1] – czas załadowania strony;
- w [2] wykonuje się cztery operacje arytmetyczne na dwóch liczbach rzeczywistych A i B;
- w [3] odpowiedź serwera zostaje zapisana w określonym obszarze strony;
- w [4] – czas wykonania obliczeń. Różni się on od czasu załadowania strony [5]. Ten ostatni jest równy [1], co wskazuje, że obszar [6] nie został ponownie załadowany. Ponadto wartości URL i [7] na stronie nie uległy zmianie.
7.3.2. Kontroler, akcje, model, widok
Tworzymy kontroler o nazwie [Premier]:
![]() |
Aby wyświetlić widok początkowy, tworzymy następującą akcję o nazwie [Action01Get]:
[HttpGet]
public ViewResult Action01Get()
{
ViewModel01 modèle = new ViewModel01();
modèle.HeureChargement = DateTime.Now.ToString("hh:mm:ss");
return View(modèle);
}
- wiersz 4: instancjonowanie modelu widoku;
- wiersz 5: inicjalizacja czasu ładowania widoku;
- wiersz 6: wyświetlenie widoku [Action10Get.cshtml] i jego szablonu.
Szablon [ViewModel01] wygląda następująco:
![]() |
using System;
using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;
namespace Exemple_04.Models
{
[Bind(Exclude = "AplusB, AmoinsB, AmultipliéparB, AdiviséparB, Erreur, HeureChargement, HeureCalcul")]
public class ViewModel01
{
// formularz
[Required(ErrorMessage="Donnée requise")]
[Display(Name="Valeur de A")]
[Range(0, Double.MaxValue, ErrorMessage = "Tapez un nombre positif ou nul")]
public double A { get; set; }
[Required(ErrorMessage = "Donnée requise")]
[Display(Name = "Valeur de B")]
[Range(0, Double.MaxValue, ErrorMessage="Tapez un nombre positif ou nul")]
public double B { get; set; }
// wyniki
public string AplusB { get; set; }
public string AmoinsB { get; set; }
public string AmultipliéparB { get; set; }
public string AdiviséparB { get; set; }
public string Erreur { get; set; }
public string HeureChargement { get; set; }
public string HeureCalcul { get; set; }
}
}
- wiersze 11–14: wartość A formularza;
- wiersze 15–18: wartość B z formularza;
- wiersze 21–24: wyniki czterech operacji arytmetycznych na A i B;
- wiersz 25: tekst ewentualnego błędu;
- wiersz 26: godzina załadowania widoku w przeglądarce;
- wiersz 27: czas obliczenia pól z wierszy 21–24;
- wiersz 7: ten szablon widoku jest jednocześnie szablonem akcji. Z tego ostatniego wyklucza się pola, które nie są przesyłane przez przeglądarkę.
Widok [Action01Get.cshtml] wygląda następująco:
![]() |
@model Exemple_04.Models.ViewModel01
@{
Layout = null;
AjaxOptions ajaxOpts = new AjaxOptions
{
UpdateTargetId = "résultats",
HttpMethod = "post",
Url = Url.Action("Action01Post"),
LoadingElementId = "loading",
LoadingElementDuration = 1000
};
}
<!DOCTYPE html>
<html lang="fr-FR">
<head>
<meta name="viewport" content="width=device-width" />
<title>Ajax-01</title>
<link rel="stylesheet" href="~/Content/Site.css" />
<script type="text/javascript" src="~/Scripts/jquery-1.8.2.min.js"></script>
<script type="text/javascript" src="~/Scripts/jquery.validate.min.js"></script>
<script type="text/javascript" src="~/Scripts/jquery.validate.unobtrusive.min.js"></script>
<script type="text/javascript" src="~/Scripts/globalize/globalize.js"></script>
<script type="text/javascript" src="~/Scripts/globalize/cultures/globalize.culture.fr-FR.js"></script>
<script type="text/javascript" src="~/Scripts/globalize/cultures/globalize.culture.en-US.js"></script>
<script type="text/javascript" src="~/Scripts/jquery.unobtrusive-ajax.js"></script>
<script type="text/javascript" src="~/Scripts/myScripts-01.js"></script>
</head>
<body>
<h2>Ajax - 01</h2>
<p><strong>Heure de chargement : @Model.HeureChargement</strong></p>
<h4>Opérations arithmétiques sur deux nombres réels A et B positifs ou nuls</h4>
@using (Ajax.BeginForm("Action01Post", null, ajaxOpts, new { id = "formulaire" }))
{
<table>
<thead>
<tr>
<th>@Html.LabelFor(m => m.A)</th>
<th>@Html.LabelFor(m => m.B)</th>
</tr>
</thead>
<tbody>
<tr>
<td>@Html.TextBoxFor(m => m.A)</td>
<td>@Html.TextBoxFor(m => m.B)</td>
</tr>
<tr>
<td>@Html.ValidationMessageFor(m => m.A)</td>
<td>@Html.ValidationMessageFor(m => m.B)</td>
</tr>
</tbody>
</table>
<p>
<input type="submit" value="Calculer" />
<img id="loading" style="display: none" src="~/Content/images/indicator.gif" />
<a href="javascript:postForm()">Calculer</a>
</p>
}
<hr />
<div id="résultats" />
</body>
</html>
- wiersz 1: szablonem widoku jest typ [ViewModel01];
- wiersz 21: typ JQuery jest wymagany zarówno do walidacji, jak i do obsługi Ajax;
- wiersze 22–23: biblioteki walidacji;
- wiersze 24–26: biblioteki internacjonalizacji;
- wiersz 27: biblioteka Ajax;
- wiersz 28: lokalna biblioteka JavaScript;
- wiersz 33: wyświetlanie czasu załadowania widoku;
- wiersz 35: formularz Ajax – wrócimy do tego później;
- wiersze 40–41: etykiety pól wprowadzania liczb A i B;
- wiersze 46–47: pola wprowadzania liczb A i B;
- wiersze 50–51: komunikaty o błędach dotyczące wprowadzania liczb A i B;
- wiersz 56: przycisk wysyłający formularz. Formularz zostanie wysłany za pomocą żądania Ajax;
- wiersz 57: obrazek oczekiwania wyświetlany podczas żądania Ajax;
- wiersz 58: link do wysłania formularza za pomocą żądania Ajax;
- wiersz 62: tag <div> o identyfikatorze [résultats]. W tym miejscu umieścimy strumień HTML zwrócony przez serwer WWW.
Ten widok wyświetla następującą stronę:
![]() |
Przyjrzyjmy się teraz kodowi, który obsługuje formularz za pomocą AJAX:
...
@{
Layout = null;
AjaxOptions ajaxOpts = new AjaxOptions
{
UpdateTargetId = "résultats",
HttpMethod = "post",
Url = Url.Action("Action01Post"),
LoadingElementId = "loading",
LoadingElementDuration = 1000
};
}
...
@using (Ajax.BeginForm("Action01Post", null, ajaxOpts, new { id = "formulaire" }))
{
....
<p>
<input type="submit" value="Calculer" />
<img id="loading" style="display: none" src="~/Content/images/indicator.gif" />
<a href="javascript:postForm()">Calculer</a>
</p>
}
...
<div id="résultats" />
- wiersz 15: zamiast używać [@Html.BeginForm], używamy [@Ajax.BeginForm]. Ta metoda dopuszcza wiele przeciążenia. Ta, której użyto, ma następującą sygnaturę:
Ajax.BeginForm(string ActionName, RouteValueDictionary routeValues, AjaxOptions ajaxOptions, IDictionary<string,object> htmlAttributes)
W tym przypadku używamy następujących parametrów rzeczywistych:
Action01Post: nazwa akcji, która przetworzy pole POST z formularza,
null: nie ma żadnych informacji o trasie do przekazania,
ajaxOpts: opcje wywołania Ajax. Zostały one zdefiniowane w wierszach 6–10,
new { id = "formulaire" }: w celu przypisania atrybutu [id='formulaire'] do wygenerowanego tagu <form>;
Zastosowano następujące opcje Ajax:
- wiersz 8: adres docelowy żądania Ajax;
- wiersz 7: metoda żądania Ajax o identyfikatorze HTTP;
- wiersz 6: identyfikator obszaru strony, który zostanie zaktualizowany w odpowiedzi na żądanie Ajax;
- wiersz 9: identyfikator obszaru strony, który zostanie wyświetlony podczas żądania Ajax – zazwyczaj jest to obrazek oczekiwania. W tym przypadku wyświetlony zostanie wiersz 20. Zawiera on animowany obrazek symbolizujący oczekiwanie. Początkowo obrazek ten jest ukryty przez styl [display : none];
- wiersz 10: czas oczekiwania w milisekundach, po upływie którego zostanie wyświetlona animacja, w tym przypadku 1 sekunda.
Kod HTML wygenerowany przez formularz Ajax wygląda następująco:
<form action="/Premier/Action01Post" data-ajax="true" data-ajax-loading="#loading" data-ajax-loading-duration="1000" data-ajax-method="post" data-ajax-mode="replace" data-ajax-update="#résultats" data-ajax-url="/Premier/Action01Post" id="formulaire" method="post"> <table>
...
<p>
<input type="submit" value="Calculer" />
<img id="loading" style="display: none" src="/Content/images/indicator.gif" />
<a href="javascript:postForm()">Calculer</a>
</p>
</form>
<hr />
<div id="résultats" />
- wiersz 1: wygenerowany tag <form>. Należy zwrócić uwagę na atrybuty [data-ajax-attr], które odzwierciedlają wartości pól obiektu typu [AjaxOptions], który został powiązany z żądaniem Ajax. Atrybuty te są obsługiwane przez bibliotekę Ajax. Bez nich tag <form> przyjmuje postać:
<form action="/Premier/Action01Post" id="formulaire" method="post">
...
<p>
<input type="submit" value="Calculer" />
<img id="loading" style="display: none" src="/Content/images/indicator.gif" />
<a href="javascript:postForm()">Calculer</a>
</p>
</form>
Mamy wówczas do czynienia z klasycznym formularzem HTML. To właśnie ten kod zostanie wykonany, jeśli użytkownik wyłączy obsługę JavaScript w swojej przeglądarce. Wiersze 5–6 pozostają wówczas niewykorzystane.
7.3.3. Akcja [Action01Post]
Akcja [Action01Post], która przetwarza żądanie Ajax HTTP, wygląda następująco:
[HttpPost]
public PartialViewResult Action01Post(FormCollection postedData, SessionModel session)
{
// symulacja oczekiwania
Thread.Sleep(2000);
// instancja modelu akcji
ViewModel01 modèle = new ViewModel01();
// czas obliczeń
modèle.HeureCalcul = DateTime.Now.ToString("hh:mm:ss");
// aktualizacja modelu
TryUpdateModel(modèle, postedData);
if (!ModelState.IsValid)
{
// zwracany jest błąd
modèle.Erreur = getErrorMessagesFor(ModelState);
return PartialView("Action01Error", modèle);
}
// co drugą próbę symuluje się błąd
int val = session.Randomizer.Next(2);
if (val == 0)
{
modèle.Erreur = "[erreur aléatoire]";
return PartialView("Action01Error", modèle);
}
// obliczenia
modèle.AplusB = string.Format("{0}", modèle.A + modèle.B);
modèle.AmoinsB = string.Format("{0}", modèle.A - modèle.B);
modèle.AmultipliéparB = string.Format("{0}", modèle.A * modèle.B);
modèle.AdiviséparB = string.Format("{0}", modèle.A / modèle.B);
// widok
return PartialView("Action01Success", modèle);
}
- wiersz 1: akcja przetwarza wyłącznie żądanie [POST];
- wiersz 2: jako szablon akcji dopuszcza:
- [FormCollection postedData]: zbiór wszystkich wartości przesłanych w żądaniu Ajax POST,
- [SessionModel session]: elementy sesji. Wykorzystano tu technikę opisaną w paragrafie 4.10;
- wiersz 2: akcja zwróci fragment HTML, a nie pełną stronę HTML;
- wiersz 5: sztucznie zatrzymujemy się na dwie sekundy, aby zasymulować długotrwałą akcję Ajax;
- wiersz 7: instancjonowany jest model typu [ViewModel01];
- wiersz 9: inicjowany jest czas obliczeniowy;
- wiersz 11: podejmowana jest próba aktualizacji modelu typu [ViewModel01] przy użyciu przesłanych wartości. Przypominamy, że są to dwie wartości: wartości liczb A i B;
- wiersz 12: sprawdzane jest, czy aktualizacja zakończyła się powodzeniem;
- wiersz 15: w przypadku błędu wypełnia się pole [Erreur] modelu;
- wiersz 16: zwracamy widok częściowy [Action01Error.cshtml] wykorzystujący szablon [ViewModel01];
- wiersze 19–24: co drugi raz symuluje się błąd;
- wiersz 19: generuje się losową liczbę całkowitą z przedziału [0,1]. Generator liczb jest pobierany z sesji;
- wiersz 20: jeśli wygenerowana wartość wynosi 0, symulowany jest błąd;
- wiersz 22: komunikat o błędzie jest umieszczany w szablonie;
- wiersz 23: zwracany jest widok częściowy [Action01Error.cshtml] wykorzystujący szablon [ViewModel01];
- wiersze 26–29: wykonywane są obliczenia arytmetyczne na liczbach A i B, a wyniki umieszczane są w modelu w postaci ciągów znaków;
- wiersz 31: zwracany jest widok częściowy [Action01Success.cshtml] wykorzystujący szablon [ViewModel01];
7.3.4. Widok [Action01Error]
Widok [Action01Error.cshtml] wygląda następująco:
@model Exemple_04.Models.ViewModel01
<h4>Résultats</h4>
<p><strong>Heure de calcul : @Model.HeureCalcul</strong></p>
<p style="color: red;">Une erreur s'est produite : @Model.Erreur</p>
Przypominamy, że ten częściowy strumień HTML zostanie wysłany w odpowiedzi na żądanie Ajax HTTP typu POST i umieszczony na stronie w obszarze o identyfikatorze [résultats]. Wszystkie te informacje pochodzą z konfiguracji Ajax używanej na stronie głównej [Action01Get.cshtml]:
@model Exemple_04.Models.ViewModel01
@{
Layout = null;
AjaxOptions ajaxOpts = new AjaxOptions
{
UpdateTargetId = "résultats",
HttpMethod = "post",
Url = Url.Action("Action01Post"),
LoadingElementId = "loading",
LoadingElementDuration = 1000
};
}
Oto przykład odpowiedzi zawierającej błąd:
![]() |
7.3.5. Widok [Action01Success]
Widok [Action01Success.cshtml] wygląda następująco:
@model Exemple_04.Models.ViewModel01
<h4>Résultats</h4>
<p><strong>Heure de calcul : @Model.HeureCalcul</strong></p>
<p>A+B=@Model.AplusB</p>
<p>A-B=@Model.AmoinsB</p>
<p>A*B=@Model.AmultipliéparB</p>
<p>A/B=@Model.AdiviséparB</p>
Również w tym przypadku ten częściowy strumień HTML zostanie wysłany w odpowiedzi na żądanie Ajax typu POST o numerze HTTP i umieszczony na stronie w obszarze o identyfikatorze [résultats]:
![]() |
7.3.6. G zyacja sesji
Zauważyliśmy, że element [Action01Post] korzysta z sesji. Szablon sesji to następujący typ [SessionModel]:
using System;
namespace Exemple_03.Models
{
public class SessionModel
{
public Random Randomizer { get; set; }
}
}
Sesja jest inicjowana w [Global.asax]:
// Sesja
protected void Session_Start()
{
SessionModel sessionModel=new SessionModel();
sessionModel.Randomizer=new Random(DateTime.Now.Millisecond);
Session["data"] = sessionModel;
}
Powiązanie sesji z modelem odbywa się w [Application_Start]:
protected void Application_Start()
{
...
// modele powiązania
ModelBinders.Binders.Add(typeof(SessionModel), new SessionModelBinder());
}
Opisano klasę [SessionModelBinder].
7.3.7. Zarządzanie obrazem oczekiwania
@model Exemple_04.Models.ViewModel01
@{
Layout = null;
AjaxOptions ajaxOpts = new AjaxOptions
{
...
LoadingElementId = "loading",
LoadingElementDuration = 1000
};
}
...
<body>
...
@using (Ajax.BeginForm("Action01Post", null, ajaxOpts, new { id = "formulaire" }))
{
...
<p>
<input type="submit" value="Calculer" />
<img id="loading" style="display: none" src="~/Content/images/indicator.gif" />
<a href="javascript:postForm()">Calculer</a>
</p>
}
...
Po uruchomieniu żądania Ajax po upływie jednej sekundy wyświetlany jest region o identyfikatorze [loading] w wierszu 7 [ligne 8]. Region ten stanowi obraz z wiersza 21, który był początkowo ukryty. Daje to następujący interfejs:
![]() |
7.3.8. Obsługa linku [Calculer]
Przyjrzyjmy się linkowi [Calculer] na stronie głównej [Action01Get.cshtml]:
<head>
<meta name="viewport" content="width=device-width" />
<title>Ajax-01</title>
...
<script type="text/javascript" src="~/Scripts/myScripts-01.js"></script>
</head>
<body>
<h2>Ajax - 01</h2>
<p><strong>Heure de chargement : @Model.HeureChargement</strong></p>
<h4>Opérations arithmétiques sur deux nombres réels A et B positifs ou nuls</h4>
@using (Ajax.BeginForm("Action01Post", null, ajaxOpts, new { id = "formulaire" }))
{
...
<p>
<input type="submit" value="Calculer" />
<img id="loading" style="display: none" src="~/Content/images/indicator.gif" />
<a href="javascript:postForm()">Calculer</a>
</p>
}
<hr />
<div id="résultats" />
- wiersz 18: kliknięcie linku [Calculer] powoduje wykonanie funkcji JS [postForm]. Jest ona zdefiniowana w pliku [myScripts-01.js] w wierszu 5. Skrypt ten wygląda następująco:
![]() |
function postForm() {
// ręcznie wykonuje się wywołanie Ajax za pomocą JQuery
var loading = $("#loading");
var formulaire = $("#formulaire");
var résultats = $('#wyniki');
$.ajax({
url: '/Premier/Action01Post',
type: 'POST',
data: formulaire.serialize(),
dataType: 'html',
begin: loading.show(),
success: function (data) {
loading.hide()
résultats.html(data);
}
})
}
// http://blog.instance-factory.com/?p=268
$.validator.methods.number = function (value, element) {
return this.optional(element) ||
!isNaN(Globalize.parseFloat(value));
}
$.validator.methods.date = function (value, element) {
return this.optional(element) ||
!isNaN(Globalize.parseDate(value));
}
jQuery.extend(jQuery.validator.methods, {
range: function (value, element, param) {
//Użyj wtyczki Globalization do analizy wartości
var val = Globalize.parseFloat(value);
return this.optional(element) || (
val >= param[0] && val <= param[1]);
}
});
Funkcje z wierszy 19–37 zostały już omówione w punkcie 6.1. Odpowiadają one za internacjonalizację stron. Nie będziemy do nich wracać. W wierszach 1–17 ręcznie wykonujemy wywołanie Ajax, które w przypadku przycisku [Calculer] było realizowane przez bibliotekę Ajax powiązaną z projektem. W tym celu wykorzystujemy bibliotekę JQuery powiązaną z projektem.
- wiersz 3: odwołanie do komponentu o identyfikatorze [loading]. [$("#loading")] zwraca zbiór elementów o identyfikatorze [loading]. Jest tylko jeden;
- wiersz 4: odwołanie do komponentu o identyfikatorze [formulaire];
- wiersz 5: odwołanie do komponentu o identyfikatorze [résultats];
- wiersz 6: wywołanie Ajax wraz z opcjami;
- wiersz 7: cel wywołania Ajax o identyfikatorze URL;
- wiersz 8: zastosowana metoda o identyfikatorze HTTP;
- wiersz 9: wysyłane dane. [formulaire.serialize] tworzy ciąg znaków [A=val1&B=val2] z pola o identyfikatorze POST w formularzu o identyfikatorze [formulaire];
- wiersz 10: oczekiwany typ danych zwracanych przez serwer. Wiadomo, że serwer zwróci strumień o nazwie HTML;
- wiersz 11: metoda do wykonania po rozpoczęciu żądania. W tym miejscu wskazujemy, że należy wyświetlić komponent o identyfikatorze [loading]. Jest to animowany obrazek oczekiwania;
- wiersz 12: metoda do wykonania w przypadku pomyślnego zakończenia żądania Ajax. Parametr [data] stanowi pełną odpowiedź serwera. Wiemy, że jest to strumień HTML;
- wiersz 13: ukrywamy sygnał oczekiwania;
- wiersz 14: aktualizujemy komponent o identyfikatorze [résultats], zastępując go wartością HTML z parametru [data].
Zachęcamy czytelnika do przetestowania linku [Calculer]. Działa on tak samo jak przycisk [Calculer], z wyjątkiem jednej nieprawidłowości w . Po skorzystaniu z tego linku można następnie przesłać nieprawidłowe wartości A i B:
![]() |
- w [1] i [2] wprowadzono nieprawidłowe wartości. Zostały one zgłoszone przez walidatory po stronie klienta;
- w przypadku [3] kliknięto link [Calculer];
- w [4] wystąpił błąd [POST], ponieważ otrzymano odpowiedź [4].
Gdy wartości są nieprawidłowe i kliknie się przycisk [Calculer], wysłanie [POST] do serwera nie następuje. W tej samej sytuacji, w przypadku linku [Calculer], żądanie [POST] do serwera jest wysyłane. Istnieje zatem zachowanie przycisku [Calculer], którego nie udało nam się odtworzyć przy użyciu linku [Calculer]. Zamiast próbować rozwiązać ten problem teraz, pozostawiamy go na później jako przykład, który zilustruje również inny problem związany z walidacją po stronie klienta.
7.4. Aktualizacja strony HTML za pomocą strumienia JSON
W poprzednim przykładzie serwer WWW odpowiadał na żądanie Ajax o numerze HTTP strumieniem o numerze HTML. W strumieniu tym znajdowały się dane wraz z formatowaniem o numerze HTML. Proponujemy powrócić do poprzedniego przykładu, tym razem z odpowiedziami JSON (JavaScript w notacji obiektowej), zawierającymi wyłącznie dane. Zaletą tego rozwiązania jest to, że w ten sposób przesyła się mniej bajtów.
7.4.1. Akcja [Action02Get]
Akcja [Action02Get] będzie punktem wejścia do nowej aplikacji. Jej kod wygląda następująco:
@model Exemple_04.Models.ViewModel02
@{
Layout = null;
AjaxOptions ajaxOpts = new AjaxOptions
{
HttpMethod = "post",
Url = Url.Action("Action02Post"),
LoadingElementId = "loading",
LoadingElementDuration = 1000,
OnBegin = "OnBegin",
OnFailure = "OnFailure",
OnSuccess = "OnSuccess",
OnComplete = "OnComplete"
};
}
<!DOCTYPE html>
<html lang="fr-FR">
<head>
<meta name="viewport" content="width=device-width" />
<title>Ajax-02</title>
....
<script type="text/javascript" src="~/Scripts/myScripts-02.js"></script>
</head>
<body>
<h2>Ajax - 02</h2>
<p><strong>Heure de chargement : @Model.HeureChargement</strong></p>
<h4>Opérations arithmétiques sur deux nombres réels A et B positifs ou nuls</h4>
@using (Ajax.BeginForm("Action02Post", null, ajaxOpts, new { id = "formulaire" }))
{
...
<p>
<input type="submit" value="Calculer" />
<img id="loading" style="display: none" src="~/Content/images/indicator.gif" />
<a href="javascript:postForm()">Calculer</a>
</p>
}
<hr />
<div id="entete">
<h4>Résultats</h4>
<p><strong>Heure de calcul : <span id="heureCalcul"/></strong></p>
</div>
<div id="résultats">
<p>A+B=<span id="AplusB"/></p>
<p>A-B=<span id="AmoinsB"/></p>
<p>A*B=<span id="AmultipliéparB"/></p>
<p>A/B=<span id="AdiviséparB"/></p>
</div>
<div id="erreur">
<p style="color: red;">Une erreur s'est produite : <span id="msg"/></p>
</div>
</body>
</html>
- wiersze 4–14: opcje wywołania Ajax;
- wiersz 10: funkcja JS do wykonania przy uruchomieniu żądania. Funkcja ta jest zdefiniowana w pliku JS, do którego odwołuje się wiersz 24;
- wiersz 11: funkcja JS, która ma zostać wykonana w przypadku niepowodzenia żądania;
- wiersz 12: funkcja JS do wykonania w przypadku pomyślnego zakończenia zapytania;
- wiersz 13: funkcja JS do wykonania po uzyskaniu wyniku żądania Ajax (niepowodzenie lub powodzenie);
- wiersze 40–43: region o identyfikatorze [entete];
- wiersze 44–49: region o identyfikatorze [résultats]. Wyświetli on wyniki czterech operacji arytmetycznych;
- wiersze 50–52: region o identyfikatorze [erreur]. Wyświetli ewentualny komunikat o błędzie.
7.4.2. Akcja [Action02Post]
Żądanie Ajax jest przetwarzane przez następującą akcję [Action02Post]:
[HttpPost]
public JsonResult Action02Post(FormCollection postedData, SessionModel session)
{
// oczekiwana symulacja
Thread.Sleep(2000);
// weryfikacja modelu
ViewModel02 modèle = new ViewModel02();
// czas ładowania i obliczeń
string HeureChargement = DateTime.Now.ToString("hh:mm:ss");
string HeureCalcul = DateTime.Now.ToString("hh:mm:ss");
// aktualizacja modelu
TryUpdateModel(modèle, postedData);
if (!ModelState.IsValid)
{
// zwracany jest błąd
return Json(new { Erreur = getErrorMessagesFor(ModelState), HeureCalcul = HeureCalcul });
}
// co drugie wywołanie symuluje błąd
int val = session.Randomizer.Next(2);
if (val == 0)
{
// zwracany jest błąd
return Json(new { Erreur = "[erreur aléatoire]", HeureCalcul = HeureCalcul });
}
// obliczenia
string AplusB = string.Format("{0}", modèle.A + modèle.B);
string AmoinsB = string.Format("{0}", modèle.A - modèle.B);
string AmultipliéparB = string.Format("{0}", modèle.A * modèle.B);
string AdiviséparB = string.Format("{0}", modèle.A / modèle.B);
// zwracane są wyniki
return Json(new { Erreur = "", AplusB = AplusB, AmoinsB = AmoinsB, AmultipliéparB = AmultipliéparB, AdiviséparB = AdiviséparB, HeureCalcul = HeureCalcul });
}
- wiersz 2: metoda zwraca typ [JsonResult], tj. tekst w formacie JSON;
- wiersz 16: informacje są zwracane w postaci instancji klasy anonimowej, zserializowanej jako JSON. Metoda [getErrorMessagesFor] została już omówiona. Ciąg znaków JSON wysyłany do przeglądarki będzie miał następującą postać:
- wiersz 31: ta sama procedura dotyczy wyników arytmetycznych. Tym razem ciąg JSON wysyłany do przeglądarki będzie miał następującą postać:
{"Erreur":"","AplusB":"4","AmoinsB":"-2","AmultipliéparB":"3","AdiviséparB":"0,333333333333333","HeureCalcul":"05:52:17"}
7.4.3. Kod JavaScript po stronie klienta
Przypomnijmy konfigurację wywołania Ajax na stronie HTML wysyłanej do przeglądarki klienta:
AjaxOptions ajaxOpts = new AjaxOptions
{
HttpMethod = "post",
Url = Url.Action("Action02Post"),
LoadingElementId = "loading",
LoadingElementDuration = 1000,
OnBegin = "OnBegin",
OnFailure = "OnFailure",
OnSuccess = "OnSuccess",
OnComplete = "OnComplete"
};
Funkcje JS, do których odwołano się w wierszach 7–10 (po prawej stronie znaku =), są zdefiniowane w następującym pliku [myScripts-02.js]:
// dane ogólne
var entete;
var loading;
var résultats;
var erreur;
var heureCalcul;
var msg;
var AplusB;
var AmoinsB;
var AmultipliéparB;
var AdiviséparB;
var formulaire;
...
function postForm() {
...
}
// podczas ładowania dokumentu
$(document).ready(function () {
formulaire = $("#formulaire");
entete = $("#entete");
loading = $("#loading");
erreur = $("#erreur");
résultats = $('#wyniki');
heureCalcul = $("#heureCalcul");
msg = $("#msg");
AplusB = $("#AplusB");
AmoinsB = $("#AmoinsB");
AmultipliéparB = $("#AmultipliéparB");
AdiviséparB = $("#AdiviséparB");
// ukrywanie niektórych elementów strony
entete.hide();
résultats.hide();
erreur.hide();
});
// rozpoczęcie
function OnBegin() {
....
}
// koniec zapytania
function OnComplete() {
...
}
// powodzenie
function OnSuccess(data) {
....
}
// błąd
function OnFailure(request, error) {
...
}
- wiersz 19: funkcja JS wykonywana po zakończeniu ładowania strony w przeglądarce;
- wiersze 20–30: pobierane są odniesienia do wszystkich interesujących nas komponentów strony. Wyszukiwanie komponentu na stronie wiąże się z pewnym obciążeniem, dlatego zaleca się przeprowadzać je tylko raz;
- wiersze 33–35: komponenty [entete], [résultats] i [loading] są ukrywane;
Po uruchomieniu żądania Ajax wykonywana jest następująca funkcja:
// rozpoczęcie
function OnBegin() {
// włączony sygnał oczekiwania
loading.show();
// niektóre elementy strony są ukrywane
entete.hide();
résultats.hide();
erreur.hide();
}
- wiersz 4: wyświetlany jest komponent [loading]. Jest to animowany obraz;
- wiersze 6–8: komponenty [entete], [résultats] i [erreur] są ukryte;
Jeśli żądanie Ajax zakończy się powodzeniem, wykonywany jest następujący kod JS:
// powodzenie
function OnSuccess(data) {
// wyświetlanie wyników
heureCalcul.text(data.HeureCalcul);
entete.show();
if (data.Erreur != '') {
msg.text(data.Erreur);
erreur.show();
return;
}
// brak błędu
AplusB.text(data.AplusB);
AmoinsB.text(data.AmoinsB);
AmultipliéparB.text(data.AmultipliéparB);
AdiviséparB.text(data.AdiviséparB);
résultats.show();
}
Aby zrozumieć ten kod, należy przypomnieć sobie dwa teksty JSON, które mogą zostać wysłane w odpowiedzi do przeglądarki:
w przypadku błędu, w przeciwnym razie ciąg:
{"Erreur":"","AplusB":"4","AmoinsB":"-2","AmultipliéparB":"3","AdiviséparB":"0,333333333333333","HeureCalcul":"05:52:17"}
Jeśli ten ciąg nazwiemy [data], wartość pola [Erreur] uzyskuje się za pomocą zapisu [data.Erreur] lub [data["Erreur"]], w zależności od wyboru. To samo dotyczy pozostałych pól ciągu JSON. Ponadto, aby przypisać tekst nieformatowany do komponentu o identyfikatorze X, należy wpisać [X.text(chaine)]. Wróćmy do kodu funkcji [OnSuccess]:
- wiersz 2: [data] to otrzymany ciąg znaków JSON;
- wiersz 4: składnik [heureCalcul] otrzymuje swoją wartość;
- wiersz 5: wyświetlany jest komponent [entete];
- wiersz 6: test pola [Erreur] w ciągu JSON;
- wiersz 7: komponent [msg] otrzymuje swoją wartość;
- wiersz 8: wyświetlany jest komponent [erreur];
- wiersz 9: zakończenie w przypadku błędu;
- wiersz 12: komponent [AplusB] otrzymuje swoją wartość;
- wiersz 13: komponent [AmoinsB] otrzymuje swoją wartość;
- wiersz 14: komponent [AmultipliéparB] otrzymuje swoją wartość;
- wiersz 15: komponent [AdiviséparB] otrzymuje swoją wartość;
- wiersz 16: wyświetlany jest komponent [résultats].
W przypadku niepowodzenia żądania Ajaxowego HTTP zostanie wykonana funkcja [OnFailure] . Niepowodzenie to jest rozpoznawane na podstawie kodu HTTP zwróconego przez serwer. Na przykład kod 500 [Internal Server Error] oznacza, że serwer nie był w stanie wykonać żądania. Funkcja [OnFailure] wygląda następująco:
// błąd
function OnFailure(request, error) {
alert("L'erreur suivante s'est produite :" + error);
}
Ograniczamy się do wyświetlenia okna dialogowego z informacją o wystąpieniu błędu. W praktyce należałoby podać bardziej szczegółowe informacje. Wkrótce zaproponujemy inne rozwiązanie.
Wreszcie funkcja [OnComplete] jest uruchamiana po zakończeniu żądania, niezależnie od tego, czy zakończyło się ono powodzeniem, czy niepowodzeniem.
// koniec zapytania
function OnComplete() {
// sygnał oczekiwania wyłączony
loading.hide();
}
Przypominamy, że to właśnie konfiguracja wywołania Ajax w widoku [Action02Get.cshtml] powoduje wywołanie tych różnych funkcji:
AjaxOptions ajaxOpts = new AjaxOptions
{
...
OnBegin = "OnBegin",
OnFailure = "OnFailure",
OnSuccess = "OnSuccess",
OnComplete = "OnComplete"
};
7.4.4. Link [Calculer]
Kod HTML linku [Calculer] w widoku [Action02Get.cshtml] wygląda następująco:
<a href="javascript:postForm()">Calculer</a>
Funkcja JS [postForm] znajduje się w zaimportowanym pliku [myScripts-02.js]:
<script type="text/javascript" src="~/Scripts/myScripts-02.js"></script>
Jej kod jest następujący:
function postForm() {
// ręczne wywołanie Ajax za pomocą JQuery
$.ajax({
url: '/Premier/Action02Post',
type: 'POST',
data: formulaire.serialize(),
dataType: 'json',
beforeSend: OnBegin,
success: OnSuccess,
error: OnFailure,
complete: OnComplete
})
}
Spotkaliśmy się już z podobnym kodem.
- wiersz 4: URL – cel wywołania Ajax;
- wiersz 5: polecenie HTTP użyte w wywołaniu Ajax;
- wiersz 6: przesłane wartości. Są one wynikiem serializacji wartości formularza. Formularz ten, oznaczony identyfikatorem [formulaire], jest odwołany przez zmienną [formulaire]. [data] będzie ciągiem znaków o postaci [A=val1&B=val2];
- wiersz 7: typ formatowania oczekiwanej odpowiedzi. Jest to ciąg znaków JSON;
- wiersz 8: funkcja JS do wykonania przy rozpoczęciu wywołania Ajax;
- wiersz 9: funkcja JS do wykonania, jeśli wywołanie Ajax zakończy się powodzeniem;
- wiersz 10: funkcja JS do wykonania w przypadku niepowodzenia wywołania Ajax;
- wiersz 11: funkcja JS do wykonania po otrzymaniu odpowiedzi z serwera, niezależnie od tego, czy zakończyło się to sukcesem, czy błędem.
Wróćmy do funkcji JavaScript, która obsługuje sytuację, w której wywołanie Ajax kończy się niepowodzeniem (wiersz 10). Wywołanie Ajax kończy się niepowodzeniem w różnych sytuacjach, na przykład gdy serwer zwraca kod błędu taki jak [403 Forbidden], [404 Not Found], [500 Internal Server Error], [301 Moved Permanently], ...
W powyższym przykładzie funkcja [OnFailure] wygląda następująco:
// błąd
function OnFailure(request, error) {
alert("L'erreur suivante s'est produite :" + error);
}
Zazwyczaj wyświetlenie obiektu [error] nie dostarcza żadnych interesujących informacji. Jeśli korzystamy z wywołania Ajax wykonanego za pomocą JQuery, możemy użyć następującej metody [OnFailure]:
// błąd
function OnFailure(jqXHR) {
alert("Erreur : " + jqXHR.status + " " + jqXHR.statusText);
msg.html(jqXHR.responseText);
erreur.show();
}
Obiekt JQuery [jqXHR] posiada następujące właściwości:
- responseText: tekst odpowiedzi serwera;
- status: kod błędu zwrócony przez serwer;
- statusText: tekst powiązany z tym kodem błędu;
- wiersz 3: wyświetla się kod błędu i odpowiadający mu opis;
- wiersz 4: umieszczamy odpowiedź serwera HTML w komponencie o identyfikatorze [msg];
- wiersz 5: wyświetla się region o identyfikatorze [erreur].
Aby przetestować tę funkcję obsługi błędów, sztucznie wygenerujemy wyjątek w akcji o identyfikatorze [Action02Post]:
[HttpPost]
public JsonResult Action02Post(FormCollection postedData, SessionModel session)
{
// sztuczny wyjątek w celu przetestowania funkcji obsługi błędów wywołania Ajax
throw new Exception();
// symulacja oczekiwania
Thread.Sleep(2000);
// walidacja modelu
...
Wiersz 5 wywołuje wyjątek. Teraz przetestujmy aplikację:
![]() |
Otrzymujemy następującą odpowiedź: [1] i [2]:
![]() |
Odpowiedź serwera pozwala nam sprawdzić, w którym wierszu kodu serwera wystąpił błąd. Często jest to przydatna informacja. Od tej pory będziemy wykorzystywać tę technikę do obsługi błędów wywołań Ajax.
7.5. Aplikacja internetowa typu single-page
Technologia Ajax pozwala tworzyć aplikacje jednostronicowe:
- pierwsza strona jest wynikiem klasycznego żądania przeglądarki;
- kolejne strony są pobierane za pomocą wywołań Ajax. W rezultacie przeglądarka nigdy nie zmienia adresu URL i nigdy nie ładuje nowej strony. Ten typ aplikacji nazywamy aplikacją jednostronicową (APU) lub, po angielsku, Single Page Application (SPA).
Oto prosty przykład takiej aplikacji. Nowa aplikacja będzie miała dwa widoki:
![]() |
![]() |
- w [1] akcja [Action03Get] pozwala nam wyświetlić pierwszą stronę, stronę 1;
- w [2] link umożliwia przejście do strony 2 za pomocą wywołania Ajax;
- w [3], URL nie uległo zmianie. Wyświetlana jest strona 2;
- w [4] link pozwala nam powrócić do strony 1 za pomocą wywołania Ajax;
- w [5] strona URL nie uległa zmianie. Wyświetlana jest strona 1.
Kod akcji [Action03Get] jest następujący:
[HttpGet]
public ViewResult Action03Get()
{
return View();
}
- wiersz 4: wyświetlany jest widok [Action03Get.cshtml].
Widok [Action03Get.cshtml] wygląda następująco:
![]() |
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Action03Get</title>
<script type="text/javascript" src="~/Scripts/jquery-1.8.2.min.js"></script>
<script type="text/javascript" src="~/Scripts/jquery.unobtrusive-ajax.min.js"></script>
</head>
<body>
<h3>Ajax - 03 - Single Page Application</h3>
<div id="content">
@Html.Partial("Page1")
</div>
</body>
</html>
- wiersze 16–18: element o identyfikatorze [content]. W tym elemencie będą wyświetlane różne strony;
- wiersz 17: domyślnie jako pierwsza wyświetli się strona [Page1.cshtml].
Strona [Page1.cshtml] wygląda następująco:
<h4>Page 1</h4>
<p>
@Ajax.ActionLink("Page 2", "Action04", new { Page = 2 }, new AjaxOptions() { UpdateTargetId = "content" })
</p>
- wiersz 1: tytuł strony, aby odróżnić ją od strony 2;
- wiersz 3: link Ajax z następującymi parametrami:
- nazwa linku [Page 2];
- działanie docelowe linku [Action04];
- parametry żądania URL. Będzie to [/Premier/Action04?Page=2];
- opcje wywołania Ajax. W tym przypadku podano jedynie identyfikator regionu, który ma zostać zaktualizowany na podstawie odpowiedzi serwera. W przypadku pozostałych opcji stosowane są wartości domyślne, o ile istnieją. Domyślną metodą dla HTTP jest GET.
Zobaczmy, co się dzieje po kliknięciu linku. Wywoływana jest metoda URL z parametrem [/Premier/Action04?Page=2]. Następnie wykonywana jest akcja [Action04]:
[HttpGet]
public PartialViewResult Action04(string page = "1")
{
string vue = "Page1";
if (page == "2")
{
vue = "Page2";
}
return PartialView(vue);
}
- wiersz 2: akcja zwraca częściowy strumień HTML;
- wiersz 2: akcja ma jako wzorzec ciąg znaków [page]. Wiemy jednak, że URL zawiera następującą informację: [/Premier/Action04?Page=2]. Przypominamy, że wzorzec nie rozróżnia wielkości liter;
- wiersze 4–8: [vue] otrzyma wartość [Page2];
- wiersz 9: zwracany jest widok częściowy [Page2.cshtml].
Widok częściowy [Page2.cshtml] wygląda następująco:
<h4>Page 2</h4>
<p>
@Ajax.ActionLink("Page 1", "Action04", new { Page = 1 }, new AjaxOptions() { UpdateTargetId = "content" })
</p>
Serwer zwraca zatem powyższy strumień HTML jako odpowiedź na wywołanie Ajax GET [/Premier/Action04?Page=2]. Przypomnijmy, że to wywołanie Ajax wykorzystuje tę odpowiedź do aktualizacji regionu o identyfikatorze [content] (wiersz 3 poniżej):
<h4>Page 1</h4>
<p>
@Ajax.ActionLink("Page 2", "Action04", new { Page = 2 }, new AjaxOptions() { UpdateTargetId = "content" })
</p>
Powoduje to wyświetlenie następującego nowego elementu o identyfikatorze [1]:
![]() |
Kierując się tym samym tokiem rozumowania, widać, że kliknięcie linku [Page 1] w [1] spowoduje wyświetlenie [2].
Wróćmy do ogólnego schematu aplikacji ASP.NET MVC:
![]() |
Dzięki kodowi JavaScript wbudowanemu w strony HTML i wykonywanemu w przeglądarce można przenieść część kodu do przeglądarki, uzyskując w ten sposób następującą architekturę:
![]() |
- w [1] warstwa internetowa ASP.NET MVC stała się internetowym interfejsem dostępu do danych, zazwyczaj przechowywanych w bazie danych. Wyświetlane widoki zawierają wyłącznie dane i nie zawierają żadnych elementów szablonowych, na przykład strumieni HTML, takich jak XML lub JSON;
- w przypadku [2]: przeglądarka wyświetla widoki statyczne (tj. niegenerowane dynamicznie) dostarczane przez serwer internetowy, który może, ale nie musi znajdować się na tym samym komputerze co serwer [1]. Te statyczne widoki są następnie wzbogacane o dane uzyskane przez JavaScript z interfejsu internetowego [1];
- kod JavaScript osadzony na stronach HTML może być podzielony na warstwy:
- warstwa [présentation] odpowiada za interakcje z użytkownikiem,
- warstwa [DAO] odpowiada za dostęp do danych za pośrednictwem serwera internetowego [1],
- warstwa [métier] odpowiada warstwie [métier], która wcześniej znajdowała się na serwerze [1], a została przeniesiona do przeglądarki [2];
Zaletą tej architektury jest to, że wymaga ona różnych umiejętności:
- kod serwera WWW [1] wymaga umiejętności związanych z .NET, ale nie wymaga umiejętności w zakresie JavaScript, HTML, CSS;
- kod osadzony w przeglądarce [2] wymaga umiejętności w zakresie JavaScript, HTML, CSS, ale nie ma znaczenia, jaką technologię wykorzystuje serwer WWW [1].
W ten sposób architektura ta ułatwia równoległą pracę zespołów o różnych kompetencjach. Szczególnie dobrze sprawdza się w przypadku aplikacji jednostronicowych.
7.6. Aplikacja internetowa typu single-page i walidacja po stronie klienta
Wcześniej wspomnieliśmy o nieprawidłowości na przykładzie Ajax-01. Przypomnijmy jej kontekst:
![]() |
- w [1] i [2] wprowadzono nieprawidłowe wartości. Zostały one zgłoszone przez walidatory po stronie klienta;
- w [3] kliknięto link [Calculer];
- w [4] wystąpił błąd [POST], ponieważ otrzymano odpowiedź [4].
Gdy wartości są nieprawidłowe i kliknie się przycisk [Calculer], wysłanie [POST] do serwera nie następuje. W tej samej sytuacji, po kliknięciu linku [Calculer], żądanie [POST] do serwera zostaje wysłane. Istnieje zatem zachowanie przycisku [Calculer], którego nie udało nam się odtworzyć w przypadku linku [Calculer].
Przeanalizujemy ten przykład w nowym kontekście: aplikacja będzie miała kilka widoków i będzie miała typ [Application à Page Unique], który właśnie opisaliśmy.
7.6.1. Widoki w przykładzie
Przykład zawiera kilka widoków:
![]() |
- w [1] – widok [Action05Get];
- w [2] – widok częściowy [Formulaire05];
- z [3] wynika widok częściowy [Failure05];
![]() |
- w [4] – widok częściowy [Success05].
Aplikacja składa się z jednej strony: jest ona ładowana przez przeglądarkę przy pierwszym żądaniu. Następnie jest aktualizowana za pomocą wywołań Ajax.
Poprzednie strony są generowane przez następujące widoki [cshtml]:
![]() |
Widok ładowany początkowo to następujący widok [Action05Get.cshtml]:
@model Exemple_04.Models.ViewModel05
@{
Layout = null;
}
<!DOCTYPE html>
<html lang="fr-FR">
<head>
<meta name="viewport" content="width=device-width" />
<title>Ajax-05</title>
<link rel="stylesheet" href="~/Content/Site.css" />
<script type="text/javascript" src="~/Scripts/jquery-1.8.2.min.js"></script>
<script type="text/javascript" src="~/Scripts/jquery.validate.min.js"></script>
<script type="text/javascript" src="~/Scripts/jquery.validate.unobtrusive.min.js"></script>
<script type="text/javascript" src="~/Scripts/globalize/globalize.js"></script>
<script type="text/javascript" src="~/Scripts/globalize/cultures/globalize.culture.fr-FR.js"></script>
<script type="text/javascript" src="~/Scripts/globalize/cultures/globalize.culture.en-US.js"></script>
<script type="text/javascript" src="~/Scripts/jquery.unobtrusive-ajax.js"></script>
<script type="text/javascript" src="~/Scripts/myScripts-05.js"></script>
</head>
<body>
<h2>Ajax - 05, Page unique - Validation formulaire côté client</h2>
<p><strong>Heure de chargement : @Model.HeureChargement</strong></p>
<h4>Opérations arithmétiques sur deux nombres réels A et B positifs ou nuls</h4>
<img id="loading" style="display: none" src="~/Content/images/indicator.gif" />
<div id="content">
@Html.Partial("Formulaire05", Model)
</div>
</body>
</html>
Należy zwrócić uwagę na następujące kwestie:
- wiersz 1: szablon widoku to typ [ViewModel05], który wkrótce omówimy;
- wiersze 13–19: znajdują się tu skrypty JavaScript niezbędne do obsługi Ajaxu i walidacji po stronie klienta;
- wiersz 20: dodamy nasze własne funkcje JavaScript w szablonie [myScripts-05.js];
- wiersz 27: animowany obrazek oczekiwania;
- wiersze 28–30: tag o identyfikatorze [content]. To właśnie w tym tagu zostaną wstawione widoki częściowe [Formulaire05, Success05, Failure05];
- wiersz 29: wstawienie widoku częściowego [Formulaire05].
Widok [Action05Get] odpowiada za wyświetlanie części [1] strony początkowej:
![]() |
Widok częściowy [Formulaire05] wygeneruje powyższą część [2]. Jego kod jest następujący:
@model Exemple_04.Models.ViewModel05
@using (Html.BeginForm("Action05Post", "Premier", FormMethod.Post, new { id = "formulaire" }))
{
<table>
<thead>
<tr>
<th>@Html.LabelFor(m => m.A)</th>
<th>@Html.LabelFor(m => m.B)</th>
</tr>
</thead>
<tbody>
<tr>
<td>@Html.TextBoxFor(m => m.A)</td>
<td>@Html.TextBoxFor(m => m.B)</td>
</tr>
<tr>
<td>@Html.ValidationMessageFor(m => m.A)</td>
<td>@Html.ValidationMessageFor(m => m.B)</td>
</tr>
</tbody>
</table>
<p>
<table>
<tbody>
<tr>
<td><a href="javascript:calculer()">Calculer</a>
</td>
<td style="width: 20px" />
<td><a href="javascript:effacer()">Effacer</a>
</td>
</tr>
</tbody>
</table>
</p>
}
- wiersz 1: widok częściowy przyjmuje jako szablon typ [ViewModel05];
- wiersz 3: formularz wygenerowany przez metodę [Html.BeginForm]. Ponieważ formularz ten zostanie wysłany za pomocą wywołania Ajax, pierwsze trzy parametry metody zostaną zignorowane. Chyba że użytkownik wyłączył obsługę JavaScript w swojej przeglądarce. W tym przypadku pomijamy tę możliwość. Czwarty parametr jest istotny. Formularz będzie miał identyfikator [formulaire];
- wiersze 5–22: formularz do wprowadzania liczb A i B;
- wiersz 27: link JavaScript, który uruchamia wykonanie czterech operacji arytmetycznych na A i B;
- wiersz 30: link JavaScript, który kasuje wprowadzone dane oraz ewentualne komunikaty o błędach z nimi związane.
Należy zauważyć, że formularz nie posiada przycisku typu [submit]. Będziemy musieli ręcznie obliczyć sumę ([Post]) wprowadzonych wartości A i B.
Jeśli nie ma błędów, wyświetlane są wyniki:
![]() |
Powyższa część [4] jest generowana przez następujący widok częściowy [Success05.cshtml]:
@model Exemple_04.Models.ViewModel05
<hr />
<p><strong>Heure de calcul : @Model.HeureCalcul</strong></p>
<p>A=@Model.A</p>
<p>B=@Model.B</p>
<h4>Résultats</h4>
<p>A+B=@Model.AplusB</p>
<p>A-B=@Model.AmoinsB</p>
<p>A*B=@Model.AmultipliéparB</p>
<p>A/B=@Model.AdiviséparB</p>
<p>
<a href="javascript:retourSaisies()">Retour aux saisies</a>
</p>
- wiersz 1: widok częściowy [Success05.cshtml] otrzymuje szablon typu [ViewModel05];
- wiersz 12: link JavaScript umożliwiający powrót do ekranów wprowadzania danych.
W przypadku błędu wyświetlany jest inny widok częściowy [3]:
![]() |
Ten widok jest generowany przez następujący kod [Failure05.cshtml]:
@model Exemple_04.Models.ViewModel05
<hr />
<p><strong>Heure de calcul : @Model.HeureCalcul</strong></p>
<p>A=@Model.A</p>
<p>B=@Model.B</p>
<h2>Les erreurs suivantes se sont produites</h2>
<ul>
@foreach (string msg in Model.Erreurs)
{
<li>@msg</li>
}
</ul>
<p>
<a href="javascript:retourSaisies()">Retour aux saisies</a>
</p>
- wiersz 1: widok częściowy [Failure05.cshtml] otrzymuje szablon typu [ViewModel05];
- wiersz 14: link JavaScript umożliwiający powrót do pola wprowadzania danych.
7.6.2. Szablon widoków
Wszystkie powyższe widoki mają ten sam szablon [ViewModel05]:
![]() |
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;
namespace Exemple_04.Models
{
[Bind(Exclude = "AplusB, AmoinsB, AmultipliéparB, AdiviséparB, Erreurs, HeureChargement, HeureCalcul")]
public class ViewModel05
{
// formularz
[Required(ErrorMessage="Donnée A requise")]
[Display(Name="Valeur de A")]
[Range(0, Double.MaxValue, ErrorMessage = "Tapez un nombre A positif ou nul")]
public string A { get; set; }
[Required(ErrorMessage = "Donnée B requise")]
[Display(Name = "Valeur de B")]
[Range(0, Double.MaxValue, ErrorMessage="Tapez un nombre B positif ou nul")]
public string B { get; set; }
// wyniki
public string AplusB { get; set; }
public string AmoinsB { get; set; }
public string AmultipliéparB { get; set; }
public string AdiviséparB { get; set; }
public List<string> Erreurs { get; set; }
public string HeureChargement { get; set; }
public string HeureCalcul { get; set; }
}
}
Jest to model [ViewModel01], który został już przedstawiony z kilkoma drobnymi zmianami:
- wiersze 15 i 19: pola A i B mają teraz typ [string], aby podczas pierwszego wyświetlenia formularza wprowadzania danych wyświetlać puste pola wprowadzania zamiast pól o wartości 0;
- wiersze 14 i 18: nie uniemożliwia to sprawdzenia wprowadzonej wartości za pomocą walidatora [Range];
- wiersz 26: lista komunikatów o błędach wyświetlana przez widok [Failure05].
7.6.3. Dane zakresu [Session]
W punkcie 7.3.6 widzieliśmy, że dane sesji były zawarte w następującym modelu [SessionModel]:
![]() |
using System;
namespace Exemple_03.Models
{
public class SessionModel
{
// generator liczb losowych
public Random Randomizer { get; set; }
}
}
Ten model sesji został rozszerzony o wartości A i B:
using System;
namespace Exemple_03.Models
{
public class SessionModel
{
// generator liczb losowych
public Random Randomizer { get; set; }
// wartości A i B
public string A { get; set; }
public string B { get; set; }
}
}
Konieczne jest bowiem zapisanie wartości A i B w sesji, jak pokazuje poniższa sekwencja:
Żądanie 1
![]() |
Żądanie 2
![]() |
W [4] odnajdujemy dane wprowadzone w [1]. Istnieją jednak dwa odrębne żądania HTTP. Wiadomo, że pamięcią między dwoma żądaniami HTTP jest sesja. Aby drugie żądanie mogło odzyskać wartości przesłane przez pierwsze, muszą one zostać zapisane w sesji.
7.6.4. Akcja serwera [Action05Get]
Akcja [Action05Get] powoduje wyświetlenie początkowej strony głównej. Jej kod wygląda następująco:
[HttpGet]
public ViewResult Action05Get()
{
ViewModel05 modèle = new ViewModel05();
modèle.HeureChargement = DateTime.Now.ToString("hh:mm:ss");
return View(modèle);
}
- wiersz 6: omówiony już widok [Action05Get.cshtml] jest wyświetlany przy użyciu szablonu typu [ViewModel05];
7.6.5. Akcja kliencka [Calculer]
Przyjrzyjmy się interakcjom użytkownika z widokami:
![]() |
Link [1] jest linkiem JavaScript:
<a href="javascript:calculer()">Calculer</a>
Funkcja JavaScript [calculer] znajduje się w pliku [myScripts-05.js]:
<script type="text/javascript" src="~/Scripts/myScripts-05.js"></script>
Kod funkcji JavaScript [calculer] jest następujący:
// dane ogólne
var content;
var loading;
function calculer() {
// najpierw odniesienia do DOM
var formulaire = $("#formulaire");
// następnie walidacja formularza
if (!formulaire.validate().form()) {
// formularz nieprawidłowy – zakończono
return;
}
// ręczne wywołanie Ajax
$.ajax({
url: '/Premier/Action05FaireCalcul',
type: 'POST',
data: formulaire.serialize(),
dataType: 'html',
beforeSend: function () {
loading.show();
},
success: function (data) {
content.html(data);
},
complete: function () {
loading.hide();
},
error: function (jqXHR) {
// wyświetlenie odpowiedzi serwera
content.html(jqXHR.responseText);
}
})
}
function retourSaisies() {
...
}
function effacer() {
...
}
// podczas ładowania dokumentu
$(document).ready(function () {
// pobieramy referencje różnych elementów strony
loading = $("#loading");
content = $("#content");
// ukrywamy animowany obraz
loading.hide();
});
- przypominamy, że kod JavaScript jest zawsze wykonywany po stronie klienta, w przeglądarce;
- wiersz 44: funkcja JS wykonywana po zakończeniu początkowego ładowania pojedynczej strony;
- wiersz 46: odwołanie do animowanego obrazu o identyfikatorze [loading];
- wiersz 47: odwołanie do regionu o identyfikatorze [content]. To właśnie ten region odbiera częściowe widoki o identyfikatorach [Formulaire05, Success05, Failure05];
- wiersze 2–3: zmienne z wierszy 46–47 są zadeklarowane jako globalne, aby inne funkcje miały do nich dostęp. Wyszukiwanie elementów na stronie wiąże się z pewnym obciążeniem (wiersze 46–47). Nie ma sensu powtarzać tego wyszukiwania, jeśli można tego uniknąć;
- wiersz 5: funkcja [calculer];
- wiersz 7: pobieramy odwołanie do formularza. Widok częściowy [Formulaire05] nadał mu identyfikator [formulaire];
- wiersz 9: ta instrukcja uruchamia walidatory formularza po stronie klienta. Właśnie tego brakowało w nieprawidłowości stwierdzonej na stronie 183. Metoda ta jest dostarczana przez bibliotekę [jquery.unobstrusive-ajax] używaną przez stronę pojedynczą:
<script type="text/javascript" src="~/Scripts/jquery.unobtrusive-ajax.js"></script>
Instrukcja zwraca [false], jeśli formularz zostanie uznany za nieprawidłowy;
- wiersz 11: wywołanie Ajax do serwera nie jest wykonywane, jeśli formularz jest nieprawidłowy;
- wiersze 14–32: wysyłane jest wywołanie Ajax do serwera;
- wiersz 15: docelowa instrukcja URL to akcja serwera [Action05FaireCalcul];
- wiersz 16: jest ona wywoływana za pomocą [POST];
- wiersz 17: wartości przesłane metodą POST. Są to dane wprowadzone w formularzu, w tym przypadku wartości A i B;
- wiersze 22–24: w przypadku pomyślnego zakończenia wywołania Ajax funkcja [calculer] aktualizuje obszar o identyfikatorze [content] za pomocą strumienia HTML wysłanego przez serwer.
Ten strumień HTML jest tym samym strumieniem, który został wysłany przez akcję [Action05FaireCalcul], do której skierowane jest wywołanie Ajax. Kod tej akcji po stronie serwera jest następujący:
[HttpPost]
public PartialViewResult Action05FaireCalcul(FormCollection postedData, SessionModel session)
{
// szablon
ViewModel05 modèle = new ViewModel05();
// czas obliczeń
modèle.HeureCalcul = DateTime.Now.ToString("hh:mm:ss");
// aktualizacja szablonu
TryUpdateModel(modèle, postedData);
if (!ModelState.IsValid)
{
// zwracany jest błąd
modèle.Erreurs = getListOfMessagesFor(ModelState);
return PartialView("Failure05", modèle);
}
...
}
- wiersz 1: akcja akceptuje wyłącznie [post];
- wiersz 2: zwraca częściowy widok;
- wiersz 2: jako parametry otrzymuje wartości przesłane metodą POST (postedData) oraz model sesji (session);
- wiersz 5: tworzony jest szablon widoku częściowego;
- wiersz 7: jest on aktualizowany o czas obliczeń;
- wiersz 9: podejmowana jest próba zastosowania przesłanych wartości do szablonu. Następnie zostaną uruchomione zawarte w nim walidatory. Można się zastanawiać, po co podejmować ten wysiłek, skoro walidatory po stronie klienta blokują POST w przypadku wprowadzenia nieprawidłowych danych. W rzeczywistości nie ma pewności co do pochodzenia POST. Być może został on wygenerowany przez kod, który nie jest naszym własnym. Dlatego zawsze należy przeprowadzać weryfikacje po stronie serwera;
- wiersz 10: sprawdzamy, czy walidatory zakończyły się powodzeniem;
- wiersz 13: jeśli szablon jest nieprawidłowy, aktualizuje się go, dodając listę błędów. Nie będziemy szczegółowo omawiać wewnętrznej metody [getListOfMessagesFor], analogicznej do metody [GetErrorMessagesFor] opisanej na stronie 65;
- wiersz 14: wyświetlany jest widok częściowy [Failure05] wraz z jego szablonem. Przypominamy kod tego widoku;
@model Exemple_04.Models.ViewModel05
<hr />
<p><strong>Heure de calcul : @Model.HeureCalcul</strong></p>
<p>A=@Model.A</p>
<p>B=@Model.B</p>
<h2>Les erreurs suivantes se sont produites</h2>
<ul>
@foreach (string msg in Model.Erreurs)
{
<li>@msg</li>
}
</ul>
<p>
<a href="javascript:retourSaisies()">Retour aux saisies</a>
</p>
- wiersze 7–12: lista błędów szablonu jest wyświetlana za pomocą tagu <ul>.
Przypomnijmy, że funkcja JS [calculer], będącapochodzi z [Post] w akcji serwera [Action05FaireCalcul], umieści ten strumień HTML w obszarze o identyfikatorze [content]. Daje to mniej więcej następujący wynik:
![]() |
Przejdźmy do dalszej analizy kodu akcji [Action05FaireCalcul]:
[HttpPost]
public PartialViewResult Action05FaireCalcul(FormCollection postedData, SessionModel session)
{
// szablon
ViewModel05 modèle = new ViewModel05();
...
// wartości A i B są zapisywane w sesji
session.A = modèle.A;
session.B = modèle.B;
// na razie brak błędów
List<string> erreurs = new List<string>();
// co drugie wywołanie symuluje błąd
int val = session.Randomizer.Next(2);
if (val == 0)
{
erreurs.Add("[erreur aléatoire]");
}
if (erreurs.Count != 0)
{
modèle.Erreurs = erreurs;
return PartialView("Failure05", modèle);
}
// obliczenia
double A = double.Parse(modèle.A);
double B = double.Parse(modèle.B);
modèle.AplusB = string.Format("{0}", A + B);
modèle.AmoinsB = string.Format("{0}", A - B);
modèle.AmultipliéparB = string.Format("{0}", A * B);
modèle.AdiviséparB = string.Format("{0}", A / B);
// widok
return PartialView("Success05", modèle);
}
- wiersz 7: model został uznany za poprawny;
- wiersze 8–9: wprowadza się do sesji wprowadzone wartości A i B. Chcemy móc je odzyskać w kolejnym zapytaniu;
- wiersze 11–22: co drugie wywołanie losowo generuje błąd;
- wiersze 24–29: wykonuje się cztery operacje arytmetyczne na wprowadzonych liczbach rzeczywistych;
- wiersz 31: zwracany jest widok częściowy [Success05] wraz z jego szablonem. Widok ten wygląda następująco:
@model Exemple_04.Models.ViewModel05
<hr />
<p><strong>Heure de calcul : @Model.HeureCalcul</strong></p>
<p>A=@Model.A</p>
<p>B=@Model.B</p>
<h4>Résultats</h4>
<p>A+B=@Model.AplusB</p>
<p>A-B=@Model.AmoinsB</p>
<p>A*B=@Model.AmultipliéparB</p>
<p>A/B=@Model.AdiviséparB</p>
<p>
<a href="javascript:retourSaisies()">Retour aux saisies</a>
</p>
Przypomnijmy, że funkcja JS [calculer], będąca[Post] w ramach akcji serwerowej [Action05FaireCalcul] umieści ten strumień HTML w obszarze o identyfikatorze [content]. Daje to mniej więcej następujący wynik:
![]() |
7.6.6. Akcja kliencka [Effacer]
Link JavaScript [Effacer] pozwala przywrócić formularz do stanu początkowego:
![]() | ![]() |
W formularzu link JS [Effacer] jest zdefiniowany w następujący sposób:
<a href="javascript:effacer()">Effacer</a>
Funkcja JS [effacer] jest zdefiniowana w pliku [myScripts-05.js] w następujący sposób:
// dane ogólne
var content;
var loading;
function calculer() {
...
}
function retourSaisies() {
...
}
function effacer() {
// najpierw odniesienia w DOM
var formulaire = $("#formulaire");
var A = $("#A");
var B = $("#B");
// przypisujemy prawidłowe wartości do wprowadzonych danych
A.val("0");
B.val("0");
// następnie zatwierdza się formularz, aby usunąć
// ewentualne komunikaty o błędach
formulaire.validate().form();
// następnie przypisuje się puste ciągi znaków do pól wprowadzania danych
A.val("");
B.val("");
}
// podczas ładowania dokumentu
$(document).ready(function () {
// pobieramy odniesienia do poszczególnych elementów strony
loading = $("#loading");
content = $("#content");
// ukrywamy animowany obraz
loading.hide();
});
- wiersze 15–17: pobierane są odniesienia do różnych elementów pliku DOM (Document Object Model);
- wiersze 19–20: w polach wprowadzania liczb A i B umieszcza się prawidłowe wartości;
- wiersz 23: uruchamia się walidatory po stronie klienta. Ponieważ wartości A i B są prawidłowe, spowoduje to zniknięcie ewentualnych komunikatów o błędach, które mogłyby zostać wyświetlone;
- wiersze 25–26: w polach wprowadzania liczb A i B umieszczamy puste ciągi znaków;
7.6.7. Akcja po stronie klienta [Retour aux Saisies]
Link JavaScript [Retour aux Saisies] umożliwia powrót do formularza po uzyskaniu wyników:
![]() | ![]() |
W formularzu link JS [Retour aux Saisies] jest zdefiniowany w następujący sposób:
<a href="javascript:retourSaisies()">Retour aux saisies</a>
Funkcja JS [retourSaisies] jest zdefiniowana w pliku [myScripts-05.js] w następujący sposób:
// dane globalne
var content;
var loading;
function calculer() {
...
}
function retourSaisies() {
// ręcznie wysyłamy żądanie Ajax
$.ajax({
url: '/Premier/Action05RetourSaisies',
type: 'POST',
dataType: 'html',
beforeSend: function () {
loading.show();
},
success: function (data) {
content.html(data);
},
complete: function () {
loading.hide();
// IMPORTANT !! walidacja
$.validator.unobtrusive.parse($("#formulaire"));
},
error: function (jqXHR) {
content.html(jqXHR.responseText);
}
})
}
function effacer() {
...
}
// podczas ładowania dokumentu
$(document).ready(function () {
// pobieramy odniesienia do różnych elementów strony
loading = $("#loading");
content = $("#content");
// ukrywamy animowany obraz
loading.hide();
});
- wiersze 11–29: wywołanie Ajax;
- wiersz 12: funkcja docelowa URL;
- wiersz 13: zostanie wywołany przez polecenie HTTP POST. Jest to POST bez przesłanych parametrów. Dlatego nie ma wiersza typu:
w wywołaniu Ajax;
- wiersz 14: oczekiwany strumień z serwera to strumień HTML;
- wiersze 18–20: ten strumień HTML posłuży do aktualizacji regionu o identyfikatorze [content];
Akcja serwera [Action05RetourSaisies] wygląda następująco:
[HttpPost]
public PartialViewResult Action05RetourSaisies(SessionModel session)
{
// widok
return PartialView("Formulaire05", new ViewModel05() { A = session.A, B = session.B });
}
- wiersz 2: akcja otrzymuje jako parametr szablon sesji, w którym wcześniej zapisaliśmy wprowadzone wartości A i B;
- wiersz 5: zwracamy widok częściowy [Formulaire05] z modelem typu [ViewModel05], w którym dbamy o zainicjowanie pól A i B wartościami A i B pobranymi z sesji;
Wróćmy teraz do kodu funkcji JavaScript [retourSaisies]:
function retourSaisies() {
// ręczne wywołanie Ajax
$.ajax({
url: '/Premier/Action05RetourSaisies',
type: 'POST',
dataType: 'html',
beforeSend: function () {
loading.show();
},
success: function (data) {
content.html(data);
},
complete: function () {
loading.hide();
// IMPORTANT !! walidacja
$.validator.unobtrusive.parse($("#formulaire"));
},
error: function (jqXHR) {
content.html(jqXHR.responseText);
}
})
}
- wiersz 13: metoda wykonywana po zakończeniu wywołania Ajax;
- wiersz 14: animowany obrazek oczekiwania jest ukrywany;
- wiersz 16: instrukcja, która jest dla mnie nieco niezrozumiała, znaleziona w sieci w celu rozwiązania następującego problemu: w formularzu wyświetlanym przez link [Retour aux saisies] walidatory po stronie klienta przestały działać. Szukając informacji na temat biblioteki JS [jquery.unobtrusive-ajax], znalazłem rozwiązanie z linii 16. Przetwarza ono formularz, prawdopodobnie w celu aktywacji walidatorów po stronie klienta.
7.7. Udostępnienie aplikacji ASP.NET w Internecie
Zobacz punkt 9.26.
7.8. Generowanie aplikacji natywnej dla systemu Android na podstawie aplikacji jednostronicowej APU
Zobacz punkt 9.27.

















































