Witaj na Zine.net online Zaloguj się | Rejestracja | Pomoc

Przyspieszamy ASP.NET - AJAX Web Services

W tej części cyklu “Przyspieszamy ASP.NET” omówię kolejne rozwiązanie z serii “wilk syty, owca cała”. Tym razem za pomocą asynchronicznego odwołania do Web Service’u sprawimy, że klient otrzyma dane szybciej, generując przy tym mniejszy ruch i mniej obciążając serwer niż w przypadku klasycznego postbacku. Co więcej, w jego odczuciu strona będzie działa bardziej dynamicznie. Jednak nie ma róży bez kolców…

Gdzie jest problem i co chcemy osiągnąć?

Przypuśćmy, że tworzymy internetowy katalog produktów. Użytkownicy będą go intensywnie przeszukiwać, filtrować, sortować i przeglądać poszczególne strony. Ponieważ tych interakcji będzie dużo, ważne dla nas jest, aby każdą z nich obsłużyć jak najsprawniej.

Nie zaskoczę nikogo mówiąc, że klasyczny postback warto w tej sytuacji zastąpić żądaniem asynchronicznym, czyli starym dobrym AJAXem. W ASP.NET mamy do wyboru dwa rozwiązania – UpdatePanel oraz AJAX Web Services. Które wybrać? Przeanalizujmy, jak wygląda pojedyncza interakcja z serwerem w powyżej opisanej sytuacji:

  Pełny Postback UpdatePanel AJAX Web Services
Żądanie do
serwera
Zawiera wszystkie pola formularza, w tym __VIEWSTATE (!) oraz __EVENTVALIDATION. Zawiera wszystkie pola formularza, w tym __VIEWSTATE (!) oraz __EVENTVALIDATION. Zawiera jedynie parametry metody w formacie JSON, np.:
{"query":"monitor"}
Przetwarzanie
na serwerze
1. Odtworzenie modelu obiektowego strony.
2. Pobranie danych z bazy.
3. Przeprowadzenie cyklu życia strony i wygenerowanie kodu HTML dla całej strony.
1. Odtworzenie modelu obiektowego strony.
2. Pobranie danych z bazy.
3. Przeprowadzenie cyklu życia strony i wygenerowanie kodu HTML dla całej strony.
Pobranie danych z bazy.
Odpowiedź
do klienta
Cały kod HTML strony. Kod HTML do wstawienia w miejsce UpdatePanelu oraz pola __VIEWSTATE, __EVENTVALIDATION i kilka innych informacji. Dane w formacie JSON.
Przetwarzanie
u klienta
Przeładowanie całej strony. Zastąpienie dotychczasowej zawartości UpdatePanelu nową. Przetworzenie w JavaScript otrzymanych danych – np. wygenerowanie interfejsu graficznego, który je prezentuje.

And the winner is… AJAX Web Services! Jak widać wyraźnie ta technika wygrywa pod względem wydajności przy pierwszych trzech kluczowych etapach interakcji. W obie strony przesyłamy mniej danych, a serwerowi odpada przetwarzanie całego modelu obiektowego strony i jej cyklu życia.

AJAX Web Services – jak to działa?

W skrócie, AJAX Web Services to rozwiązanie, które pozwala nam wygodnie komunikować się z WebServicem z poziomu JavaScriptu. Dokładnie rzecz ujmując, sprawia, że:

  • komunikacja z WebServicem może odbywać się również w formacie JSON (JavaScript Object Notation), który jest bardziej naturalny dla JavaScriptu niż XML (oraz bardziej zwięzły),
  • wywołanie operacji usługi sprowadza się do wywołania zwykłej metody w JavaScript
  • przy komunikacji z WebServicem pozwala na używanie “normalnych” obiektów i kolekcji.

Spójrzmy na prosty przykład. Oto nasz WebService:

   1: [WebService(Namespace = "http://tempuri.org/")]
   2: [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
   3: [ScriptService]
   4: public class CatalogService : WebService
   5: {
   6:     [WebMethod]
   7:     public IList<Product> FindProducts(string query)
   8:     {
   9:         var products = DataSource.FindProductsInDB(query);
  10:         return products;
  11:     }
  12: }

Od zwykłego WebService’u różni się tylko tym, że ma dodatkowo atrybut [ScriptService]. Możemy w nim dowolnie posługiwać nawet obiektami zdefiniowanymi przez nas – w tym wypadku jest to klasa Product. Teraz spójrzmy na stronę (wyglądem nie powala, ale nie to tutaj chodzi ;)):

image

Interesujący nas kawałek jej kodu:

   1: <asp:ScriptManager runat="server">
   2: <Services>
   3:     <asp:ServiceReference Path="~/CatalogService.asmx" />
   4: </Services>
   5: </asp:ScriptManager>
   6:  
   7: Wyszukaj produkty:<br /> 
   8: <asp:TextBox runat="server" ID="tbQuery" Width="400px" /> 
   9: <asp:Button runat="server" ID="btSearch" Text="Wyszukaj" 
  10:     OnClientClick="BLOCKED SCRIPTdoSearch(); return false;" />
  11: <div runat="server" id="resultsContainer"></div>

Jedyną kwestią, na którą chciałbym tutaj zwrócić uwagę, to linię 3, w których instruujemy ASP.NET, żeby dołączył specjalny JavaScript, który pozwoli nam się odwoływać do usługi “CatalogService.asmx”. Po stronie HTMLa daje to mniej więcej taki efekt:

<script src="CatalogService.asmx/jsdebug" type="text/javascript"></script>

Teraz sprawa najważniejsza – kod JavaScript, który wywoła usługę, pobierze dane i wyświetli je:

   1: function doSearch(){
   2:     var query = $get('<%= tbQuery.ClientID %>').value;
   3:     $get('<%= resultsContainer.ClientID %>').innerHTML = "Wyszukuję...";
   4:     MyShop.CatalogService.FindProducts(query, searchSuccess, searchFailed);
   5: }
   6: function searchSuccess(results, userContext, methodName) {
   7:     var cntr = $get('<%= resultsContainer.ClientID %>');
   8:     cntr.innerHTML = results.length == 0 ? "Brak produktów." : "";
   9:     
  10:     for (var i=0; i<results.length; i++) {
  11:         var element = document.createElement("div");
  12:         element.appendChild(
  13:             document.createTextNode(results[i].Name + " - " + results[i].Price));
  14:         cntr.appendChild(element);
  15:     }
  16: }
  17: function searchFailed(error, userContext, methodName) {
  18:     $get('<%= resultsContainer.ClientID %>').innerHTML = "Wystąpił błąd.";
  19: }
Najważniejsza jest linia 4, która pokazuje jak wygodne jest odwołanie do WebService’u. Jak widać jest to po prostu “statyczna” metoda, której argumenty to kolejno:
  1. Wszystkie parametry wymagane przez usługę; w naszym wypadku jest tylko jeden – query.
  2. Funkcję, która ma zostać wywołana po otrzymaniu odpowiedzi (opcjonalnie); u nas – searchSuccess.
  3. Funkcję, która ma zostać wywołana w razie problemów z połączeniem lub błędu na serwerze (opcjonalnie); u nas – searchFailed.
  4. Dowolny obiekt, który stanowić będzie kontekst dla tego zapytania (opcjonalnie). W powyższym przykładzie nie używam tego parametru. Gdybyśmy jednak wielokrotnie wywoływali metodę usługi, to dzięki wartości przekazanej w tym parametrze moglibyśmy zidentyfikować, do którego zapytania odnosi się każda z odpowiedzi.

Funkcja wywoływana po uzyskaniu odpowiedzi (linia 6) ma 3 parametry – rezultat zwrócony przez WebService, kontekst wywołania (o którym pisałem w pkt. 4) oraz nazwę wywoływanej operacji. Analogicznie zbudowana jest metoda wywoływana w przypadku błędu (problem z komunikacją, wyjątek na serwerze, itp), tyle że pierwszy argument zawiera informacje o błędzie.

Warto zwrócić uwagę na miły fakt, iż otrzymany wynik jest tablicą obiektów zawierających takie pola jak klasa Product w C#. Przykładowo, linia 13 zawiera odwołania do poszczególnych pól (“results[i].Name”).

Kolejną dobrą wiadomością jest to, że razem z wywołaniami asynchronicznymi wysyłane są wszystkie ciasteczka. Dzięki temu użytkownik na serwerze jest uwierzytelniany i wiemy, kto odwołuje się do Web Service’u.

Zobaczmy naszą stronę w działaniu:

image
image

Jeżeli chcesz dowiedzieć się więcej o ASP.NET AJAX Web Services, to polecam stronę.

Nie ma róży bez kolców…

Jeżeli spojrzymy na powyższe listingi, to wyraźnie zauważymy, że używając AJAXowych odwołań do Web Service’ów będziemy musieli napisać sporo JavaScriptu. Fani JS nie uznają tego za wadę, ale mam wrażenie, że większość programistów ASP.NET (w tym ja) dużo bardziej komfortowo czuje się w czysto obiektowym i silnie typowanym świecie .NET.

Mała praktyczna uwaga

Przy oczekiwaniu na dane przy AJAXowym wywołaniu, trzeba wyświetlić użytkownikowi informacje, że dane są pobierane, żeby wiedział że coś się dzieje. Dodanie do tego jakiejś ładnej, niezbyt dużej animacji, sprawia, że oczekiwanie jest milsze i strona robi wrażenie bardziej dynamicznej. Takie kręcące się kółka, przesuwające się paski i inne cuda można sobie wygenerować np. na stronie: http://www.ajaxload.info/.

Podsumowanie

Wiem, że pisząc o asynchronicznych odwołaniach do Web Service’ów, Ameryki nie odkrywam. Większość z nas o tym słyszała, a część na pewno stosuje w swoich projektach. Niemniej jednak, chcę tym wpisem zwrócić uwagę na wykorzystanie tej techniki właśnie w kwestii optymalizacji działa witryn, a nie tylko uatrakcyjnienia ich w oczach użytkowników.

Opublikowane 25 września 2009 21:14 przez jakubin

Komentarze:

# Jakub Binkowski - dot or not : Przyspieszamy ASP.NET - AJAX Web Services

Dziękujemy za publikację - Trackback z dotnetomaniak.pl

25 września 2009 22:26 by dotnetomaniak.pl

# re: Przyspieszamy ASP.NET - AJAX Web Services

bardzo ciekawe, dzieki

27 września 2009 18:07 by binary

# re: Przyspieszamy ASP.NET - AJAX Web Services

Dzięki za inspirację, wydaje się proste. Będę się dziś bawił.

30 września 2009 13:45 by kwis

# re: Przyspieszamy ASP.NET - AJAX Web Services

Wielka szkoda, ze narzut ASP.NET AJAX (ScriptManager, generowane proxy) jest spory :(

15 października 2009 20:26 by MJ

# re: Przyspieszamy ASP.NET - AJAX Web Services

A jak z dostępem do sesji - czy można włączyć dostęp do sesji? Czy może raczej należy używać tego mechanizmu jako mechanizmu dostępu do danych (coś jak warstwy "usług danych" w trybie read-only)? Czasami chcę uzależnić kod znajdujący się w metodzie webowej od różnych stanów aplikacji, np. od tego do jakiej grupy należy użytkownik. Posiadam taką informację w sesji, teraz więc jak będzie lepiej: nie używać sesji i wszystkie dane przekazywać jako parametr do WebService, czy lepiej włączyć sesję i mieć problem z głowy. Pozdrawiam.

3 lutego 2010 14:23 by MB

# re: Przyspieszamy ASP.NET - AJAX Web Services

@MB:

W metodzie można mieć dostęp do sesji - wystarczy atrybut:

[WebMethod(EnableSession=true)]

Jakie dane warto trzymać w sesji? Tutaj odpowiedź zależy od kontekstu. Takie dane jak role świetnie się do tego nadają, zwłasza, że przekazywanie ich jako parametr byłoby straszną luką bezpieczeństwa.

Ja osobiście preferuje traktowanie takich web service'ów jako właśnie warstwy "usług danych" (nie koniecznie read-only) i raczej nie korzystam z sesji.

3 lutego 2010 14:53 by jakubin

# re: Przyspieszamy ASP.NET - AJAX Web Services

According to my own monitoring, thousands of persons on our planet receive the <a href="http://bestfinance-blog.com/topics/personal-loans">personal loans</a> at various creditors. Thence, there's great possibilities to receive a student loan in every country.

19 marca 2011 23:59 by HERMINIA29DAVID
Komentarze anonimowe wyłączone

About jakubin

MVP w kategorii C#, MCP. Aktualnie pracuje w Webstruments.pl jako programista C#.