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.

opublikowano przez jakubin | 24 komentarzy

Krótka refleksja o Imagine Cup 2010

Właśnie przeczytałem wiadomość, iż ruszyła kolejna edycja konkursu Imagine Cup 2010 – chyba najważniejszej rangą tego typu imprezy na świecie. I po raz kolejny przypomniałem sobie, że, niestety, nie jestem już studentem i już nigdy nie zrealizuje mojego planu wystartowania w tym konkursie…

Dlatego apeluję do wszystkich, którzy mają jakiś pomysł, ale wahają się lub zwyczajnie nie mogą się zabrać do pracy – nie idźcie tą drogą! Zbierzcie świetną ekipę, wymyślcie super rozwiązanie i wygrajcie konkurs. Studia naprawdę są krótkie i decyzji o starcie nie można odkładać w nieskończoność. Powodzenia!

opublikowano przez jakubin | 21 komentarzy
Filed under:

Przyspieszamy ASP.NET - CSS Sprites

Zwykle na bogaty interfejs graficzny witryny składa się wiele obrazków. Nawet, gdy każdy z nich zajmuje po klika kilobajtów, to czas ich pobierania może zacząć dominować przy ładowaniu strony. W końcu załadowanie każdej grafiki wymaga nawiązania oddzielnego połączenia z serwerem. A gdyby tak dało się przesłać wszystkie obrazki w ramach jednego żądania do serwera… Da się i technika ta nazywa się CSS Sprites!

Koncepcja, którą przedstawię w tym wpisie, nie jest w żaden sposób powiązana z ASP.NET – można ją stosować na dowolnej stronie HTML, niezależnie, jaka technologia znajduje się na serwerze. Stanowi ona jeden z ważniejszych sposobów optymalizacji witryn WWW, dlatego uważam, iż nie można o niej zapomnieć, pracując nad wydajnością aplikacji ASP.NET.

W czym problem?

Przeanalizujmy pewien przykład:
http://demos.telerik.com/ribbonbardemo/Examples/Client/ClientMockup.aspx 
Na powyższej stronie znajduje się bardzo ciekawy przykład aplikacji ASP.NET, która wykorzystuje interfejs graficzny z Office 2007 i została zrealizowana przez jednego z użytkowników kontrolek Telerika. Z tą stroną jest jednak pewien problem – zobaczmy, jak wygląda jej pierwsze załadowanie:

  Ilość żądań Czas [s] Rozmiar [KB]
Obrazki 112 (81%) 3,96 (77%) 122 (29%)
Pozostałe
(HTML, JS, CSS)
26 (19%) 1,21 (23%) 298 (71%)

Do jej załadowania przeglądarka musi wysłać aż 138 żądań do serwera, z czego aż 112 przypada na obrazki. Paradoksalnie, choć rozmiar wszystkich obrazków stanowi jedynie 29% całego transferu, to na ich pobranie poświęcanych jest aż 77% czasu.

“Duszki” CSS

Technika CSS Sprites polega na zastąpieniu wielu małych obrazków jednym dużym. Takie rozwiązanie ma kilka zalet:

  1. Redukcja ilości żądań do serwera – jeden obrazek to jedno połączenie.
  2. Mniejszy transfer - jeden zbiorczy obrazek zajmuje mniej przestrzeni niż wiele mniejszych.
  3. Układ graficzny strony pojawia się od razu, a nie ładuje w kawałkach.
  4. Brak efektu “doładowywania” się grafik od dynamicznych elementów interfejsu graficznego (np. krawędzi menu pojawiającego się po kliknięciu).

Jak to wygląda w rzeczywistości? Zobaczmy sprite z biblioteki jQuery UI:

sprite

Jeden obrazek
Żądania do serwera: 1
Rozmiar: 8 KB

Oddzielne obrazki
Żądania do serwera: 173
Rozmiar: 28 KB

Teraz przejdźmy do meritum – jak sprawić, żeby zamiast dużego obrazka pokazał się jego wycinek? Zabawa sprowadza się do manipulacji 5 ustawieniami CSS:

  • width – szerokość elementu (obrazka)
  • height – wysokość elementu (obrazka)
  • background-image – obrazek tła
  • background-position – przesunięcie obrazka tła
  • background-repeat – powtarzanie obrazka tła
Przypadek 1: Sprite w elemencie blokowym

Jeżeli chcemy wstawić sprite w elemencie blokowym (np. <div>, <p> lub inny z ustawionym stylem “display: block”), to sprawa jest prosta. Przykładowo, żeby wyświetlić kopertę z powyższego obrazka (7 rząd, 6 kolumna) potrzebny jest następujący kod CSS i HTML:

   1: <style type="text/css">
   2: .icon-mail 
   3: {
   4:     width: 16px; /*szerokość sprite'a*/
   5:     height: 16px; /*wysokość sprite'a*/
   6:     background-image: url(sprite.png); /*obrazek zbiorczy*/
   7:     background-position: -80px -96px; /* -(6-1)*16px -(7-1)*16px */
   8:     background-repeat: no-repeat;
   9: }
  10: </style>
  11: ...
  12: <div class="icon-mail"></div>

Krótkie słowo wyjaśnienia, skąd wzięły się wartości w background-position:

  • -80px – gdyż obrazek zbiorczy trzeba przesunąć o 5*16px w lewo, aby ikonka koperty znalazła się tuż przy jego lewej krawędzi.
  • -96px – gdyż obrazek zbiorczy trzeba przesunąć o 6*16px do góry, aby ikonka koperty znalazła się tuż przy jego górnej krawiędzi.

Oczywiście, znacznie wygodniej jest utworzyć sobie kilka klas obsługujących cały obrazek, np.

   1: <style type="text/css">
   2: .icon16x16
   3: {
   4:     width: 16px; 
   5:     height: 16px; 
   6:     background-image: url(sprite.png); 
   7:     background-repeat:no-repeat;
   8: }
   9: .sprite16x16-1-1 { background-position: 0px 0px; }
  10: .sprite16x16-1-2 { background-position: -16px 0px; }
  11: .sprite16x16-1-3 { background-position: -32px 0px; }
  12: .sprite16x16-2-1 { background-position: 0px -16px; }
  13: .sprite16x16-2-2 { background-position: -16px -16px; }
  14: .sprite16x16-2-3 { background-position: -32px -16px; }
  15: </style>
  16: ...
  17: <div class="icon16x16 sprite16x16-2-2"></div>

 

Przypadek 2: Sprite w elemencie “inline”

Często potrzebujemy, aby sprite zachowywał się jak zwykły obrazek w tagu <img>, który można umieszczać np. w tekście. Innymi słowy, żeby zachowywał się jak element typu “inline”. Problem polega na tym, że takim elementom nie można ustawiać wysokości ani szerokości, więc gdybyśmy wstawili sprite w element <span>, to nie wyświetliłby się poprawnie.

Jednym z rozwiązań może być ustawienie “display: inline-block” (CSS 2.1). Przykładowy kod wygląda następująco:

   1: <style type="text/css">
   2: .icon16x16
   3: {
   4:     display: inline-block;
   5:     width: 16px; 
   6:     height: 16px; 
   7:     background-image: url(sprite.png); 
   8:     background-repeat:no-repeat;
   9: }
  10: .sprite16x16-1-1 { background-position: 0px 0px; }
  11: .sprite16x16-1-2 { background-position: -16px 0px; }
  12: .sprite16x16-2-1 { background-position: 0px -16px; }
  13: .sprite16x16-2-2 { background-position: -16px -16px; }
  14: </style>
  15: ...
  16: Oto ikonka <span class="icon16x16 sprite16x16-2-2"></span> w tekście.

Powyższe rozwiązanie ma pewne wady związane z niepełną/niepoprawną interpretacją “inline-block” przez starsze przeglądarki. Nie zadziała to w FireFoxie 2.0 i starszych, natomiast w IE 6 i 7 element, któremu ustawiamy “display: inline-block” musi mieć oryginalnie “display: inline” (dlatego <span> dobrze nadaje się do tego celu).

Przypadek 3: Sprite w elemencie o zmiennej szerokości lub wysokości

Technika CSS Sprites może być również zastosowana do obrazków, które mają zmienną szerokość lub wysokość. Przypuśćmy, iż chcemy zrobić ładny pasek postępu w trzech kolorach: zielonym, żółtym i czerwonym. Oczywiście, każdy pasek będzie miał szerokość proporcjonalną do prezentowanego postępu. Wówczas tworzymy obrazek, w którym pionowo jeden pod drugim umieszczamy wypełnienia dla pasków:
ProgressBarFill
Teraz możemy użyć następującego kodu:

   1: <style type="text/css">
   2: .progressbar
   3: {
   4:     width: 100px;
   5:     height: 10px;
   6:     border: solid 1px;
   7:     margin: 2px;
   8: }
   9: .progressbar>div
  10: {
  11:     height: 10px;
  12:     background-image: url(ProgressBarFill.png);
  13:     background-repeat: repeat-x;
  14: }
  15: .progressbar-red { background-position: 0px 0px; }
  16: .progressbar-yellow { background-position: 0px -10px; }
  17: .progressbar-green { background-position: 0px -20px; }
  18: </style>
  19: ...
  20: <div class="progressbar"><div style="width: 20px" class="progressbar-red"></div></div>
  21: <div class="progressbar"><div style="width: 50px" class="progressbar-yellow"></div></div>
  22: <div class="progressbar"><div style="width: 90px" class="progressbar-green"></div></div>

Efekt będzie następujący:
progress

W przypadku elementów o zmiennej wysokości postępujemy analogicznie.

Przecież to tyle pracy…

Jeżeli uważasz, że przygotowanie obrazków, klas CSS oraz przebudowanie stron, aby używały CSS Sprites, to dużo pracy… masz rację. Ale zawsze są narzędzia, które nam tą pracę ułatwiają (na przykład tutaj możemy połączyć obrazki i wygenerować do nich klasy CSS). Poza tym, jest to wysiłek jednorazowy.

Podsumowanie

Mam nadzieję, że tym wpisem przekonam kogoś do stosowania CSS Sprites. Oczywiście, gdy interfejs naszej witryny składa się z trzech obrazków na krzyż, to gra nie jest warta świeczki. Natomiast, przy większej ich ilości wydaje mi się, iż efekt końcowy rekompensuje pracę, którą trzeba wykonać na początku.

opublikowano przez jakubin | 27 komentarzy

Przyspieszamy ASP.NET - kompresja HTTP

Wyobraźmy sobie rozwiązanie, które:

  • pozwala na zmniejszenie transferu wykorzystywanego przez witrynę,
  • umożliwia szybsze dostarczenie treści użytkownikowi,
  • wymaga bardzo małego nakładu pracy przy wdrożeniu,
  • w niedużym stopniu wpływa na wydajność serwera oraz
  • jest od dawna wspierane przez wszystkie przeglądarki…

Zbyt piękne, żeby było prawdziwe? Nieprawda – takie rozwiązanie istnieje i nazywa się kompresja HTTP! Idea polega w skrócie, żeby odpowiedź na żądanie klienta skompresować przed wysłaniem, ograniczając jej rozmiar i czas przesyłania przez sieć. Efekt: wilk syty, owca cała – zmniejszamy transfer witryny (nasze koszty) i czas ładowania stron (lepszy odbiór przez użytkowników).

Jak to działa?

Przeglądarka wysyła do serwera żądanie, którego nagłówek wygląda mniej więcej tak:
image 
W tym nagłówku ważny jest nagłówek Accept-Encoding, w którym przeglądarka chwali się jakie rodzaje kodowania (czyt. kompresji) obsługuje. Dostępne są dwie standardowe metody: GZip oraz Deflate. Wówczas serwer wie, że może skompresować przesyłaną zawartość i że klient go zrozumie. Nagłówek odpowiedzi może wyglądać mniej więcej tak:
image 
Jak widać nagłówek Content-Encoding informuje klienta, jaka metoda kodowania (kompresji) została zastosowana do przesłania treści odpowiedzi.

A teraz rzecz najważniejsza. Kompresja HTTP oraz metody GZip i Deflate są częścią standardu HTTP/1.1 (opublikowanego w 1999r.) i wspierają je wszystkie współczesne przeglądarki – począwszy od IE 4.0 i Netscape 4.0.

Oczywiście, nie wszystko warto kompresować. O ile zawartość tekstowa (ASPX, HTML, JS, CSS) w wyniku kompresji zmniejsza swój rozmiar średnio o 75% (staje się 4 razy mniejsza!), to w przypadku plików już skompresowanych (grafika, wideo, docx, itd.) zwykle uzyskamy efekt zerowy.

Nie ma róży bez kolców? Niekoniecznie. Jedyną wadą kompresji HTTP jest trochę większe zużycie procesora, potrzebne do skompresowania wysyłanej treści. Jednakże w większości rozwiązań webowych wąskie gardło stanowi szybkość dysków lub ilość pamięci operacyjnej, podczas gdy czasu procesora mamy pod dostatkiem (oczywiście są wyjątki).

Kompresja HTTP z punktu widzenia serwera

Wyróżniamy dwa rodzaje kompresji z punktu widzenia serwera:

  1. Kompresja statyczna, która dotyczy statycznych plików serwowanych bezpośrednio przez serwer (np. pliki HTML, JS, CSS). Ponieważ ich zawartość nie zmienia się w czasie, to serwer może raz skompresowany plik zachować w swoim cache’u, dzięki czemu przy następnym żądaniu nie musi przeprowadzać kompresji na nowo.
  2. Kompresja dynamiczna, dotycząca zawartości generowanej dynamicznie (ASPX, ASMX, ASHX, AXD, itp.). W tej sytuacji każda odpowiedź jest inna i serwer musi za każdym razem przeprowadzać kompresję.

Kompresja HTTP na IIS 6

Uruchomienie kompresji w II 6 możliwe jest albo z poziomu konsoli administracyjnej albo wykorzystując odpowiedni skrypt. Wyczerpująco opisuje to artykuł “Enabling HTTP Compression (IIS 6.0)”, więc nie ma sensu, żebym go tutaj przepisywał. Natomiast jeśli chodzi o konfigurację to polecam tekst “Using HTTP Compression for Faster Downloads (IIS 6.0)”.

Z ważnych informacji – IIS 6 pozwala na wybór, kiedy stosowana jest kompresja. Niestety, to rozróżnienie bazuje tylko i wyłącznie na rozszerzeniach plików. W przypadku zawartości statycznej nie stanowi to problemu. Natomiast w przypadku treści generowanej dynamicznie jest to spore ograniczenie. Najlepszym przykładem mogą być tutaj HTTP Handlers (rozszerzenie ASHX), w przypadku których typ dostarczanej zawartości zależy tylko i wyłącznie od konkretnego handlera.

Kompresja HTTP na IIS 7

W przypadku IIS 7 uruchomienie kompresji wymaga dwóch kroków. Po pierwsze musimy upewnić się, iż mamy zainstalowane odpowiednie składniki serwera (“Kompresja zawartości statycznej” i “Kompresja zawartości dynamicznej”). Po drugie musimy włączyć kompresję w pliku web.config lub z poziomu konsoli IIS Manager. Uruchomienie i konfigurację kompresji HTTP na IIS 7 bardzo dobrze opisuje artykuł “HTTP Compression ”.

IIS 7, w odróżnieniu od poprzedniej wersji, pozwala decydować o stosowaniu kompresji w oparciu o typ MIME zawartości (nagłówek Content-Type odpowiedzi). Dzięki temu, zwłaszcza w przypadku zawartości dynamicznej mamy dostępne znacznie precyzyjniejsze narzędzie.

Z ciekawych możliwości wprowadzonych w IIS 7 jest możliwość określenia progów zużycia procesora, dla których wyłączana jest kompresja oraz włączana jest ponownie (jeśli taka możliwość istniała w IIS 6, to poprawcie mnie).

Kompresja HTTP ręcznie

Czasami może zdarzyć się, iż nie możemy włączyć kompresji bezpośrednio na serwerze. Najczęściej bywa tak w przypadku hostingu aplikacji ASP.NET (shared hosting). Wówczas w interesie hostingodawcy (wg GIODO takie słowo istnieje ;)) jest abyśmy z kompresji nie korzystali, gdyż za transfer płacimy, a za czas procesora zwykle nie. Istnieje jednak programistyczne obejście pozwalające włączyć kompresję przynajmniej w tych sytuacjach, gdy żądanie nie jest obsługiwane bezpośrednio przez serwer i dociera do silnika ASP.NET (w przypadku IIS 6 jest to tylko zawartość dynamiczna; w przypadki IIS 7 zależy to od konfiguracji).

Oto przykładowy fragment pliku Global.asax.cs uruchamiający kompresję HTTP ręcznie dla stron ASP.NET:

   1: protected void Application_BeginRequest(object sender, EventArgs e)
   2: {
   3:     if (this.Request.Headers["Accept-Encoding"] != null
   4:         && this.Request.Headers["Accept-Encoding"].Contains("gzip")
   5:         && this.Request.Path.ToLower().EndsWith(".aspx"))
   6:     {
   7:         this.Response.Filter = new System.IO.Compression.GZipStream(this.Response.Filter, System.IO.Compression.CompressionMode.Compress, true);
   8:         this.Response.AddHeader("Content-encoding", "gzip");
   9:     }
  10: }

Podsumowanie

Kompresja HTTP:

  • wchodzi w skład standardu HTTP/1.1,
  • jest wpierana przez praktycznie wszystkie współczesne przeglądarki,
  • wymaga bardzo małego nakładu pracy do uruchomienia,
  • zmniejsza transfer wykorzystywany przez witrynę,
  • skraca czas ładowania stron,
  • ale zwiększa zużycie procesora.

Czy warto? A czy szybciej biega się z pięćdziesięciokilogramowym workiem na plecach czy bez?

opublikowano przez jakubin | 29 komentarzy

Przyspieszamy ASP.NET - wstęp

Od dłuższego czasu w pracy zajmuję się tworzeniem biznesowej aplikacji webowej. Słowem wstępu, jest to system wspierający przygotowywanie projektów dofinansowanych z EFS PO KL, zrealizowany w modelu Software as Service (reklama dla zainteresowanych: www.webefs.pl). Aplikację tworzymy głównie z wykorzystaniem ASP.NET 3.5 (klasycznego – nie MVC), a w tym ASP.NET AJAX. Z ciekawostek ważnych w kontekście tego wpisu, jej interfejs graficzny zbudowaliśmy w oparciu o Office Ribbon UI, a więc jest relatywnie bogaty.

Nie tak dawno musiałem stawić czoła chyba najstarszemu problemowi w informatyce – system działa wolno. Niestety, problem nie zależał od obciążenia serwera; był zauważalny przy wręcz prawie zerowym ruchu. Dlatego optymalizację logiki aplikacji oraz jej interakcji z bazą danych, o których mniejsze lub większe pojęcie miałem, musiałem zostawić na później i zabrać się za szukanie rozwiązania gdzieś indziej. Otóż tzw. “wolne działanie” miało dwa najważniejsze symptomy:

  1. Strony ładowały się długo.
  2. Użytkownicy mieli odczucie, iż strony po załadowaniu działały wolno – jakby z opóźnieniem (chodzi tutaj o takie kwestie jak JavaScript, efekty “hover” czy samo przewijanie strony).

I najważniejsze – o ile w Chrome i Firefox prędkość była znośna, o tyle w IE (zwłaszcza 7, mniej 6 i 8) nie dało się praktycznie korzystać z aplikacji.

Oczywiście, osoby zajmujące się od dłuższego czasu tworzeniem witryn webowych, na pewno wiedzą na czym może tutaj polegać problem i jak mu zaradzić. Dla mnie jednak w owym czasie była to terra ignota. Na szczęście szybko trafiłem na bardzo ciekawą stronę:

Best Practices for Speeding Up Your Web Site

W najbliższym cyklu postów postaram się przedstawić wnioski, jakie wysnułem podczas wdrażania części z rozwiązań zaprezentowanych w powyższym artykule. W szczególności postaram się skoncentrować na praktycznych aspektach optymalizacji aplikacji web w technologii ASP.NET AJAX na serwerze IIS 6 i 7. Mam nadzieję, ta wiedza komuś się kiedyś przyda.

Niestety, w projekcie, nad którym pracuję, nie przyjąłem żadnych naukowych metod mierzenia prędkości działania stron, więc nie dysponuję danymi pokazującymi czarno na białym, iż wprowadzone rozwiązania przyczyniły się do np. skrócenia czasu ładowania stron przeciętnie o x %. Musicie zadowolić się jedynie stwierdzeniem, iż różnicę widać gołym okiem i jest znacząca ;).

Dla zachęty do przeczytania przyszłych postów z tej serii dodam, iż niektóre z prezentowanych rozwiązań są prawie idealne – wymagają małego nakładu pracy, dają widoczny efekt i praktycznie nie powodują efektów ubocznych.

Pierwszy odcinek z tej serii – już niebawem!

opublikowano przez jakubin | 29 komentarzy
Filed under: ,

Czy warto organizować Speaker Idole?

W ostatni czwartek odbył się konkurs Speaker Idol w Łodzi, w wyniku którego wyłoniliśmy prezentera do CodeCamp 2009 Warszawa. Było to pierwsze tego typu wydarzenie organizowane przez Łódzką Grupę Specjalistów IT & .NET oraz pierwsze, w którym brałem udział (zarówno jako uczestnik jak i organizator). Pomyślałem, że jest to dobra okazja do podzielenia się swoimi przemyśleniami, zwłaszcza, że wiele z lokalnych grup (zwanych też nie po polsku “społecznościami offline”) jeszcze tego typu imprezy u siebie nie organizowało.

Tytułem wstępu jedno zdanie o samej idei Speaker Idol. Jest to konkurs, polegający na tym, iż uczestnicy wygłaszają krótkie prezentacje na wybrany przez siebie temat, a publiczność wybiera w głosowaniu zwycięzcę. Koniec wstępu.

Plany, nadzieje, obawy

Podejmując decyzję o ogłoszeniu konkursu liczyliśmy na osiągnięcie trzech celów:

  1. Wyłonienie nowych potencjalnych prelegentów spośród członków grupy
    Wyszliśmy z założenia, że przygotowanie 5-10 minutowej prezentacji, nie wymaga aż tak wiele czasu i wysiłku jak w wypadku godzinnej prezentacji, a perspektywa nagród na pewno zadziała zachęcająco. Poza tym osoba, która już raz stanęła przed publicznością, może chcieć spróbować własnych sił po raz kolejny – tym razem przygotowując pełną sesję.
  2. Zwiększenie liczby aktywnych członków grupy.
    Wszyscy liderzy chcieliby, aby sale podczas spotkań ich grupy pękały w szwach i nie było to spowodowane przeniesieniem się do mniejszej sali ;). Speaker Idol oraz wiążące się z nim kilka krótkich prezentacji na różne tematy, to na pewno nowa formuła spotkania. Liczyliśmy, że będzie zachęcająca dla obecnych członków.
  3. Wyłonienie zwycięzcy, który przygotuje dobrą sesję na CodeCamp.

Najbardziej obawialiśmy się tego, że nikt się nie zgłosi. W końcu znacznie bardziej prestiżowe edycje Speaker Idoli (przed MTS czy C2C) miały większe lub mniejsze problemy z rekrutacją uczestników. W tym celu podjęliśmy kilka kroków zaradczych, które w większości się sprawdziły:

  • Konkurs ogłosiliśmy z ponad miesięcznym wyprzedzeniem. 
    Oczywiście, prawda jest taka, że każdy i tak  swoją prezentację opracowywał maksymalnie tydzień wcześniej, ale świadomość posiadania większej ilości czasu daje większe poczucie swobody.
  • Konkurs odbył się zaraz po długim weekendzie.
    Dzięki temu było więcej czasu na przygotowanie prezentacji w ostatnim krytycznym tygodniu.
  • Postaraliśmy się, aby nagrody były ciekawe (własna sesja na CodeCamp, MSDN Premium z Visual Studio Team Suite, 7xWindows Vista Ultimate i inne).

Poza tym baliśmy się też tego, czy prezentacje będą na dobrym poziomie, zwłaszcza, że wysyłamy naszego reprezentanta-zwycięzcę na CodeCamp w Warszawie.

Ostatnią naszą obawą była frekwencja na spotkaniu, tzn. czy publiczność (jurorzy) dopisze?

Jak wyszło?

Zgłoszeń w sumie było 5, czyli wynik nie taki zły. Oczywiście, prawie wszystkie pojawiły się praktycznie w ostatniej chwili, ale jest to chyba normalne (zresztą sam swoją zgłosiłem kilka dni przed ostatecznym terminem). Prezentacje dotyczyły następujących tematów:

  1. “Gdy DataGridView nie wystarczy - prezentacja pakietu DXperience” (Rafał Sańda).
  2. “Przyspieszamy aplikacje Web 2.0” (moja).
  3. “Checked exceptions w C#” (Bartek Legiędź).
  4. “XP Mode w Windows 7” (Darek Porowski).
  5. “Jak się ma dzisiaj moja firma ? - czyli jak wykorzystywać wskaźniki w SSRS 2008” (Mariusz Koprowski).

Muszę przyznać, że obawa o poziom była absolutnie nieuzasadniona! Każda prezentacja (oczywiście nie piszę tu o własnej, bo nie mi ją oceniać) była bardzo dobrze przygotowana, interesująca i z przyjemnością się jej słuchało. Całkowicie zasłużenie zwyciężył Bartek, który wybrał świetny temat, bardzo ciekawie go opracował i przy okazji ma naturalne zdolności do prowadzenia sesji.

Co do ilości zgłoszeń - 5 to całkiem niezła liczba, jednak wszystkie zgłoszenia pochodziły od tak zwanego “core” grupy, czyli osób najbardziej zaangażowanych w jej życie. Trzeba tu jednak zaznaczyć, iż to nikt z nas nie wystartował w konkursie, żeby nie było pusto, ale dlatego, że miał ciekawy temat, którym chciał się podzielić z innymi. Spośród uczestników był tylko jeden - Rafał, który nie przygotował wcześniej żadnej prezentacji na spotkaniach grupy.

Ostatnią obawą była frekwencja. Niestety, na spotkanie przyszło trochę mniej osób niż zwykle, ale tutaj bardziej obwiniłbym piękną wiosenną pogodę niż samą formułę Speaker Idola.

Najważniejsze pytanie – czy warto?

Z czystym sumieniem mogę stwierdzić, że nasz Speaker Idol był sukcesem, choć trochę innym niż oczekiwałem. Z jednej strony tylko częściowo udało nam się zrealizować postawione cele, ale z drugiej wyszło jedno z ciekawszych spotkań, przynajmniej wg mnie.

Co się udało?

  • Wyłoniliśmy naprawdę dobrego reprezentanta na CodeCamp.
  • Zorganizowaliśmy bardzo ciekawe spotkanie, które ze względu na swoją formułę różniło się od pozostałych.
  • W moim odczuciu trochę bardziej zintegrowaliśmy się jako grupa (w końcu społeczność to nie sala i prezentacje, tylko ludzie i relacje między nimi).
  • Nowa osoba spróbowała swoich sił w roli prelegenta i przygotowała naprawdę niezłą sesję.

Co się nie do końca udało?

  • W konkursie nie wzięły udziału “nowe twarze”, na co, przyznam szczerze, dosyć mocno liczyliśmy.
  • Frekwencja mogła być trochę lepsza. Albo pogoda albo nowa formuła nie przekonała do siebie niektórych. Ich strata :P.

Czy warto było organizować Speaker Idol? Zdecydowanie tak!

opublikowano przez jakubin | 20 komentarzy

Ballmer, Ballmer, Ballmer

Bilet PKP Łódź-Warszawa-Łódź - 60 zł.

Czas poświęcony na przejazdy - 3,5 godz.

Możliwość zadania pytania prezesowi Microsoft - bezcenna?

No właśnie - pytanie brzmi: czy warto było przybyć na 50 minutowe spotkanie ze Stevem Ballmerem? Wszystko zależy od tego, czego oczekiwało się po tym spotkaniu. Za tym, żeby poświęcić pół dnia (w tym bezcenny poranek) przemawiały:

  • możliwość zobaczenia na żywo człowieka, który "stoi za tym wszystkim",
  • możliwość zadania mu pytania i
  • możliwość w przyszłości rzucenia mimochodem podczas rozmowy "podczas jednego z moich ostatnich spotkań z Ballmerem mówił, że..." ;).

Natomiast siedzący na drugim ramieniu diabełek (pingwin?) podpowiadał:

  • wyśpij się,
  • i tak nie dowiesz się niczego wybitnie nowego,
  • będzie transmisja on-line,
  • wyśpij się,
  • to jest spotkanie otwarte, więc Steve Ballmer nie będzie mógł zdradzić żadnych tajemnic.

Raz się żyje - zarejestrowałem się i zgłosiłem własne 4 pytania. Ku mojemu zaskoczeniu, do zadania wytypowane zostały aż dwa i to dokładnie te, na których najbardziej mi zależało.

Przybyłem na miejsce. Po prezentacji kilku klipów promocyjnych Microsoftu, na salę wszedł Steve w... biało-czerwonym szaliku z napisem "akslop". Na szczęście, w odróżnieniu od pewnego wysoko postawionego polskiego polityka, zauważył swój błąd, odwrócił szalik i okazało się że "akslop" to "Polska". Wstęp całkiem miły. Potem powiedział parę słów od siebie - coś o wizji, przyszłości, kryzysie. Następnie przeszliśmy do pytań. W sumie było ich ponad 10, z czego ja pytałem się o:

  1. Co sądzi Pan o patentach na oprogramowanie?
    Trochę liczyłem tutaj na krytykę postawy "patentuj wszystko". Dostałem odpowiedź: "Patenty są bardzo ważne dla branży, jednakże prawodawstwo wymaga dopracowania, gdyż czasami prowadzi do nadużyć.". Nic odkrywczego, choć trudno się z tą odpowiedzią nie zgodzić.
  2. Jaka będzie przyszłości Internet Explorera? Czy będziecie go rozwijać, czy też kupicie Operę lub stworzycie coś własnego?
    Na to pytanie odpowiedź była konkretna: "tak - będziemy rozwijać IE, nie - nie kupimy Opery, bo nie zgodziłyby się na to instytucje antymonopolowe". Dodatkowo Steve zasugerował, że Microsoft stworzy coś nowego (czyżby Gazelle?) albo źle go zrozumiałem.

Co jeszcze zapadło mi w pamięci? Odpowiedź na pytanie, czy kryzys wpłynie na popularność darmowych rozwiązań - "nie - tylko zwiększy skalę piractwa". A także prośba o określenie całego spektrum działań Microsoftu jednym słowem. Ktoś na sali podpowiedział "awesome", co podchwycił i błyskotliwie rozwinął Steve - "awesome, baby!".

Generalnie prezes Microsoftu zrobił na mnie dokładnie takie wrażenie, jakiego się spodziewałem. Jest to człowiek o niezwykłej charyzmie, dużej spontaniczności, przechodzącej czasami w lekką odmianę szaleństwa (developers, developers, developers), potrafiący być momentami rozbrajająco szczery oraz świetnie czującym publiczność.

Czy było warto? Uważam, że tak.

opublikowano przez jakubin | 13 komentarzy
Filed under: ,

Sesja na CodeCamp, MSDN Premium i Windows Vista w 10 minut, czyli Speaker Idol Łódź!

silodz

W imieniu Łódzkiej Grupy Profesjonalistów IT & .NET mam zaszczyt zaprosić wszystkich do wzięcia udziału w konkursie Speaker Idol 2009!

Wystarczy przygotować krótką prezentację:

  • na dowolny temat,
  • trwającą max. 10 minut
  • i wygłosić ją na spotkaniu Grupy 7 maja.

Co można wygrać?

  • Własna sesja na CodeCamp 2009 Warszawa!
  • Roczna subskrypcja MSDN Premium z Visual Studio 2008 Team Suite*
    MSDN Premium zawiera systemy operacyjne Microsoft, serwery i narzędzia z przeznaczeniem do tworzenia oprogramowania i testowania, Microsoft Office do dowolnego zastosowania biznesowego, 4 zdarzenia pomocy technicznej i inne.
  • 7 x Windows Vista Ultimate*
  • Windows Compute Cluster Server 2003*
  • JetBraints ReSharper*
  • Perpetuum Software*
  • CodeSmith Tools*
  • Plecaki Microsoft

* licencje NFR (not for resale - bez prawa odsprzedaży), nie mające wartości rynkowej.

Więcej informacji znajduje się na stronie Łódzkiej Grupy Profesjonalistów IT & .NET.

opublikowano przez jakubin | 22 komentarzy

String.Format() w .NET i JavaScript (ASP.NET AJAX)

Swojego czasu z wielką radością odkryłem, iż biblioteka ASP.NET AJAX dostarcza genialne funkcje w JavaScript, działające dokładnie tak samo jak String.Format w .NET:

  • String.format(format, args) - formatuje tekst,
  • String.localeFormat - formatuje tekst używając bieżącej kultury UI (w ScriptManager należy dać EnableScriptGlobalization="true" oraz wybrać odpowiednią kulturę na poziomie strony lub aplikacji).

Innymi słowy pisząc w BLOCKED SCRIPT

String.formatLocale("Aktualny miesiąc to {0:MMMM yyyy}", new Date())

uzyskujemy oczekiwany tekst - np. "Aktualny miesiąc to luty 2009". Genialne, prawda?

Niestety, wersja JavaScript jest lekko upośledzonym odpowiednikiem swojego .NETowego brata :(. Po pierwsze, nie obsługuje wszystkich ciągów formatujących - to akurat można przeżyć. Po drugie, w języku polskim w przypadku dat popełnia błędy gramatyczne, co już jest większym problemem... Jakie błędy? Oto proste zestawienie:

Format .NET JavaScript
d 2009-02-22 2009-02-22
D 22 lutego 2009 22 luty 2009
f 22 lutego 2009 22:07 -
F 22 lutego 2009 22:07:19 22 luty 2009 22:07:20
g 2009-02-22 22:07 -
G 2009-02-22 22:07:19 -
M 22 lutego 22 luty
O 2009-02-22T22:07:19.9917540+01:00 -
R Sun, 22 Feb 2009 22:07:19 GMT -
s 2009-02-22T22:07:19 2009-02-22T22:07:20
t 22:07 22:07
T 22:07:19 22:07:20
u 2009-02-22 22:07:19Z -
U 22 lutego 2009 21:07:19 -
Y luty 2009 luty 2009
MMMM luty luty
d MMMM yyyy 22 lutego 2009 22 luty 2009
MMMM yyyy luty 2009 luty 2009

Jak widać różnica dotyczy przypadku, w którym wyświetlany jest dzień i nazwa miesiąca. Poprawna jest wersja generowana przez metodę w .NET - "22 lutego 2009", czyli miesiąc powinien być w dopełniaczu. JavaScript używa niepoprawnego przypadku - mianownika ("22 luty 2009"). Najdziwniejsze w tym wszystkim jest to, że nic nie stało na przeszkodzie, aby formatowanie było poprawne. Dane dotyczące kultury zawierają nazwy miesięcy w dopełniaczu:

var __cultureInfo = '{"name":"pl-PL",
"numberFormat":{"CurrencyDecimalDigits":2,"CurrencyDecimalSeparator":",","IsReadOnly":true,"CurrencyGroupSizes":[3],"NumberGroupSizes":[3],"PercentGroupSizes":[3],"CurrencyGroupSeparator":" ","CurrencySymbol":"zł","NaNSymbol":"nie jest liczbą","CurrencyNegativePattern":8,"NumberNegativePattern":1,"PercentPositivePattern":1,"PercentNegativePattern":1,"NegativeInfinitySymbol":"-nieskończoność","NegativeSign":"-","NumberDecimalDigits":2,"NumberDecimalSeparator":",","NumberGroupSeparator":" ","CurrencyPositivePattern":3,"PositiveInfinitySymbol":"+nieskończoność","PositiveSign":"+","PercentDecimalDigits":2,"PercentDecimalSeparator":",","PercentGroupSeparator":" ","PercentSymbol":"%","PerMilleSymbol":"‰","NativeDigits":["0","1","2","3","4","5","6","7","8","9"],"DigitSubstitution":1},
"dateTimeFormat":{
    "AMDesignator":"","Calendar":{"MinSupportedDateTime":"\/Date(-62135596800000)\/","MaxSupportedDateTime":"\/Date(253402297199999)\/","AlgorithmType":1,"CalendarType":1,"Eras":[1],"TwoDigitYearMax":2029,"IsReadOnly":true},"DateSeparator":"-","FirstDayOfWeek":1,"CalendarWeekRule":2,"FullDateTimePattern":"d MMMM yyyy HH:mm:ss","LongDatePattern":"d MMMM yyyy","LongTimePattern":"HH:mm:ss","MonthDayPattern":"d MMMM","PMDesignator":"","RFC1123Pattern":"ddd, dd MMM yyyy HH\u0027:\u0027mm\u0027:\u0027ss \u0027GMT\u0027","ShortDatePattern":"yyyy-MM-dd","ShortTimePattern":"HH:mm","SortableDateTimePattern":"yyyy\u0027-\u0027MM\u0027-\u0027dd\u0027T\u0027HH\u0027:\u0027mm\u0027:\u0027ss","TimeSeparator":":","UniversalSortableDateTimePattern":"yyyy\u0027-\u0027MM\u0027-\u0027dd HH\u0027:\u0027mm\u0027:\u0027ss\u0027Z\u0027","YearMonthPattern":"MMMM yyyy",
    "AbbreviatedDayNames":["N","Pn","Wt","Śr","Cz","Pt","So"],
    "ShortestDayNames":["N","Pn","Wt","Śr","Cz","Pt","So"],
    "DayNames":["niedziela","poniedziałek","wtorek","środa","czwartek","piątek","sobota"],
    "AbbreviatedMonthNames":["sty","lut","mar","kwi","maj","cze","lip","sie","wrz","paź","lis","gru",""],
    "MonthNames":["styczeń","luty","marzec","kwiecień","maj","czerwiec","lipiec","sierpień","wrzesień","październik","listopad","grudzień",""],
    "IsReadOnly":true,"NativeCalendarName":"Kalendarz gregoriański",
    "AbbreviatedMonthGenitiveNames":["sty","lut","mar","kwi","maj","cze","lip","sie","wrz","paź","lis","gru",""],
    "MonthGenitiveNames":["stycznia","lutego","marca","kwietnia","maja","czerwca","lipca","sierpnia","września","października","listopada","grudnia",""]}}';

Niestety, wartości z pola MonthGenitiveNames nie są nigdzie potem wykorzystywane w kodzie JavaScipt ASP.NET AJAX, co świadczy o pewnym niechlujstwie twórców biblioteki.

opublikowano przez jakubin | 12 komentarzy
Filed under: , , ,

Eleganckie samobójstwo aplikacji ASP.NET

Czasami może się zdarzyć, że nasza aplikacja ASP.NET odczuje potrzebę popełnienia samobójstwa i narodzenia się na nowo. Kiedy? Na przykład, gdy zmieni się jakiś plik konfigurujący aplikację (nie dotyczy to web.config, którego zmiana powoduje automatyczny restart) lub gdy chcemy w sekcji administracyjnej witryny dać możliwość administratorowi wykonania tego na życzenie. Wówczas pojawia się pytanie, jak to zrobić elegancko i zgodnie z lege artis?

Rozwiązanie łopatologiczne polega na modyfikacji pliku web.config (choćby metodą File.SetLastWriteTime) i pozwolenie, żeby środowisko ASP.NET zajęło się resztą.

Rozwiązanie bardziej eleganckie: HttpRuntime.UnloadAppDomain(). Wówczas środowisko kulturalnie kończy swoje działanie, a aplikacja jest uruchamiana ponownie przy następnym żądaniu. Wady? Niestety, wymaga full trust.

opublikowano przez jakubin | 10 komentarzy
Filed under: ,

Grupa Łódź ruszyła!

Z wielką radością informuję, że już niedługo zakończy się niechlubny okres, kiedy to drugie trzecie miasto w Polsce nie miało swojej społeczności entuzjastów technologii Microsoft. Już w połowie grudnia rozpocznie swoją działalność od dawna oczekiwana Łódzka Grupa Profesjonalistów IT & .NET!

Interesują nas wszelkie zagadnienia związane z technologiami Microsoft - od szeroko pojętej tematyki IT Pro, przez SQL Server, aż po tworzenie oprogramowania w oparciu o platformę .NET.

Pierwsze spotkanie odbędzie się 16 grudnia 2008 - Wydział Chemii Uniwersytetu Łódzkiego w Łodzi przy ul. Tamka 12 - więcej szczegółów i agenda spotkania już za kilka dni.

Wszystkie osoby i firmy zainteresowane życiem grupy lub chcące aktywnie włączyć się w jej działalność, zapraszamy na stronę:
http://www.grupa-lodz.org

lub zachęcam do kontaktu mailowego: kontakt [monkey] grupa-lodz [dot] org.

opublikowano przez jakubin | 23 komentarzy

?

?
opublikowano przez jakubin | 8 komentarzy

Wnioski po MTS 2008

Microsoft Technology Summit 2008 zakończył się. Niestety, co bardziej aktywni blogerzy już dawno zdążyli opisać swoje wrażenia, więc moja relacja byłaby równie gorąca i na czasie co otwierająca MTS sesja o historii wstążki w Office 2007. Dlatego nie napiszę kolejnej relacji, a kilka wniosków, jakie wyciągnąłem z tej konferencji.

Jak w przyszłości wybierać sesje?

  1. Sprawdzeni prezenterzy.
    Są takie osoby, na których sesje można pójść na ślepo. W moim przypadku byli to Tadeusz Golonka, Bartosz Pampuch i Tomasz Kopacz. Żaden z nich mnie nie zawiódł. Po MTS do listy doszło jeszcze jedno nazwisko - Bartłomiej Zass.
  2. Wybierać tematy, o których nic lub prawie nic nie wiesz.
    Prawda znana od lat... Tylko nie wiedzieć czemu, zawsze przy rejestracji o niej zapominałem i wybierałem znane mi tematy. Bo niby poziom 400, więc pewnie dowiem się wielu nowych szczegółów. Na MTSie trochę przypadkowo wybrałem się na sesję o programowaniu mobilnym. Kiedyś trochę bawiłem się Compact Framework 3.5, ale krótko i bez robienia jakiś zaawansowanych rzeczy. I ta właśnie sesja okazała się być idealna dla mnie - Bartłomiej Zass bardzo ciekawie zaprezentował, jak wielkie możliwości daje CF 3.5 + Visual Studio 2008 w zakresie programowania na platformę Windows Mobile. Nie zdawałem sobie sprawy, że takie czynności jak odczytywanie pozycji przez GPS, interakcja z telefonem (SMSy, telefony), czy zaawansowana emulacja urządzenia (utrata zasięgu, rozładowanie baterii) są tak proste w realizacji.
  3. Nie sugerować się zbytnio poziomem.
    Niestety, nie zawsze sesje bywają rzeczywiście na takim poziomie, jaki jest deklarowany. W przypadku wyboru prezentacji "Wydajne aplikacje ASP.NET w świecie Web 2.0" kierowałem się poziomem 400 i... bardzo się zawiodłem. Na początku dowiedzieliśmy się, jak uruchomić na stronie ASP.NET AJAX ("a teraz wstawiamy kontrolkę UpdatePanel"), co to jest LINQ i LINQ2SQL oraz jak zrobić wydajne stronicowanie. Jedynym przykładem na poziomie był Single Sign On dla kilku aplikacji webowych (na przykładzie własnej witryny ASP.NET i Community Servera).
  4. Nie sugerować się zbytnio tematem.
    Czasami bywa tak, że nie do końca precyzyjnie sformułowany temat może błędnie zachęcić lub zniechęcić do zapisania się na sesję. W moim wypadku o mały włos zrezygnowałbym ze świetnej sesji Bartka Pampucha pt. ".NET bez wizardów – sposoby tworzenia i dynamicznego aktywowania komponentów w aplikacjach", gdyż po przeczytaniu tematu, nie za bardzo wiedziałem o czym będzie sesja.
  5. Nie sugerować się zbytnio firmą prezentera.
    Często gdy prelegent pracuje w dużej, renomowanej firmie, to wydaje nam się, że wiedza, jaką może nam przekazać będzie wyjątkowa. Zwłaszcza kiedy tą firmą jest... Agencja Bezpieczeństwa Wewnętrznego, a tematyką "Bezpieczeństwo serwisów WWW – praktyczne uwagi o implementacji zaleceń DBTI ABW w ASP.NET". Trochę naiwnie uznałem, iż ABW posiada jakąś tajemną wiedzę z zakresu zabezpieczeń i przekaże ją nam podczas sesji. Naiwny ja! Sama prezentacja była jak najbardziej w porządku, ale rewolucji w moim postrzeganiu bezpieczeństwa aplikacji ASP.NET nie spowodowała.

Cechy najlepszych sesji i prezenterów

  1. Nic nie zastąpi doświadczenia. Niestety.
    Cóż, nie zaskoczę nikogo twierdząc, że im większe doświadczenie ma prezenter w danej dziedzinie tym zrobi lepszą prezentację. Opisanie dziesiątek teorii, wykonanych badań i najlepsze zebranie materiału z książek, nie zastąpi własnych przemyśleń i rzeczywistych problemów. Świetnym przykładem jest tutaj Tadeusz Golonka, mający przynajmniej kilkunastoletnie doświadczenie w prowadzeniu projektów i zespołów. Na MTS mówił o zarządzaniu ryzykiem ("Efektywne zarządzanie ryzykiem bez/z Team Foundation Server 2008?"). Temat niby ważny i znany w teorii, ale w praktyce ograniczający się do zrobienia listy ryzyk i umieszczenia jej w czeluściach dokumentacji projektu, której nikt nie czyta. Na początku T. Golonka "pocieszył" publiczność - większość kierowników, z którymi miał kontakt nie zarządza ryzykiem w projektach i nie robi tego, bo nie wie jak. A więc ja, skromny programista, miałem też prawo mieć o tym pojęcie, delikatnie mówiąc, mgliste. Następnie, pokazał w praktyce, jak powinno takie zarządzanie wyglądać.
  2. Zaskoczyć publiczność.
    Nic tak nie zapada w pamięci, jak prezentacja inna niż wszystkie. Po CodeCamp 2008 w Krakowie oraz MTS widzę, że Bartosz Pampuch jest specjalistą w tej dziedzinie. Na CodeCamp była prezentacja o agile... poprowadzona w stylu agile, gdzie zespół (publiczność) decydował o omawianych zagadnieniach. A na MTS był... rzutnik z dotykowym ekranem (WTF?) oraz prezentacja w WPF zamiast PowerPoint. Nie ukrywam, że przez pierwsze 5 minut prezentacji nie słuchałem w ogóle Bartka tylko zastanawiałem się, jakim cudem zwykły nieduży rozwijany ekran rzutnika może reagować na gesty dłoni?! Odpowiedź była prosta - http://www.cs.cmu.edu/~johnny/projects/wii/. W skrócie: reakcja nie na gesty dłoni, tylko na lampkę na podczerwień; nie ekran dotykowy, tylko kamera na podczerwień śledząca do 4 punktów (kontroler Wii) i przesyłająca dane przez bluetooth do komputera + soft zamieniający obraz na ruch myszki. Koszt: ok. 100 zł. Wrażenia publiczności: bezcenne.
  3. Nie tracić czasu.
    Dobrym przykładem "czasopożeraczy" są kawałki kodu pisane na żywo. Ale jak się ich pozbyć? Nie można usunąć z prezentacji przykładów, bo bez nich trudno pokazać, jak działa jakaś technologia. Z drugiej jednak strony, teksty w stylu "a teraz dodajemy referencję do projektu... czekamy aż się pojawi okienko... i nadal czekamy" lub "a teraz kompilujemy... czekamy... aplikacja się uruchamia..." nie pobudzają specjalnie słuchaczy.
    Bartosz Pampuch świetnie i niezwykle prosto sobie z tym poradził - cały pisany kod nagrał wcześniej jako screencast'y i po prostu puszczał je jako przykłady. Dzięki temu - kod zawsze działał, dłuższe i mniej ciekawe fragmenty obejrzeliśmy w trybie fast-forward, a ważniejsze momenty powoli. Nie można nie zauważyć w tym momencie świetnego przygotowania Bartka. Każde nagranie miało idealnie dobrane tempo, tak że komentarz dokładnie mieścił się w wyznaczonych ramach. Co więcej, cała sesja była świetnie rozplanowana, nic nie było pominięte lub przegadane. Sam jakiś czas temu miałem przyjemność prowadzić prezentację na spotkaniu WG.NET o podobnej tematyce (AOP i DI). W pewnym momencie trwania sesji Bartka stwierdziłem, że to co mi zajęło pół godziny, on powiedział w 5 minut. Ogólnie - wielkie brawa za tą prezentację!
  4. Wciągnąć publiczność do prezentacji.
    Interakcja z publicznością bywa ryzykowna, nie mniej jednak dobrze poprowadzona potrafi bardzo skutecznie przykuć uwagę. Tak było w przypadku sesji Bartłomieja Zassa pt. "Programowanie mobilne". Pytanie: jak dobrze zilustrować przykład reagowania na zdarzenia smartphona, np. w postaci przychodzącego SMSa? Odpowiedź: napisać aplikację zliczającą SMSy, pokazać ekran komórki na rzutniku i zachęcić publiczność do głosowania "czy obiad był dobry?". Zabawa była ciekawa, zwłaszcza gdy okazało się, że SMSy o treści innej niż przewidziana wyświetlają się jako nowe wiadomości, co można łatwo wykorzystać (wbrew planom prezentera) do zorganizowania chat'u na żywo na wielkim ekranie sali kongresowej :).
opublikowano przez jakubin | 14 komentarzy
Filed under: ,

Przygody z Policy Injection Application Block

Policy Injection Application Block w skrócie to biblioteka pozwalająca na wstrzyknięcie kodu opakowującego wywołania metod. Dzięki temu za pomocą jednego atrybutu lub odpowiedniego wpisu w pliku konfiguracyjnym, możemy "nakazać", aby np. czas wykonania metody był mierzony i logowany. Oczywiście, różnych takich zastosowań możemy wyobrazić sobie bardzo wiele. Tyleż samo widać korzyści z zastosowania tego podejścia. Programowanie aspektowe (inaczej AOP - aspect oriented programming), o którym właśnie mowa, pozwala ograniczyć ilość powtarzalnego kodu, tak aby programista mógł skoncentrować na implementowanej funkcjonalności, a architekt mógł zaimplementować raz a dobrze kluczowe kwestie (logowanie, obsługa wyjątków, transakcje, itp.).

Pierwsze zauroczenie...

Moją przygodę na poważnie z PIAB zacząłem jakiś czas temu, kiedy postanowiłem wykorzystać ten blok w jednym z projektów. Spośród istniejących rozwiązań wybrałem właśnie blok z Enterprise Library, kierując się przy tym po części instynktem, a po części zaufaniem do technologii sygnowanych przez samą grupę Patterns & Practices z Microsoft. Początki były bardzo miłe - zalety programowania aspektowego są oczywiste. Ponadto, wraz z PIAB dostajemy zestaw gotowych aspektów (a właściwie "call handlers"), integrujących blok z resztą biblioteki. Tak więc wstrzyknięcie kodu do logowania, obsługi wyjątków, walidacji, autoryzacji, itd. uzyskujemy dodając w najprostszym przypadku jeden atrybut.

Wykorzystanie samego PIAB jest proste i nie wymaga żadnych skomplikowanych czynności. Jeżeli planujemy dodawać aspekty tylko poprzez atrybuty, to nawet nie potrzebujemy dodatkowych wpisów w pliku konfiguracyjnym. Wystarczy tylko w specjalny sposób "opakować" obiekt, w który chcemy wstrzyknąć dodatkową funkcjonalność. Oto prosty przykład pokazujący, w jaki sposób uzyskać walidację parametrów metody:

public interface ILogic
{
    [ValidationCallHandler]
    void SayMyName([NotNullValidator]string name);
}

public class Logic : ILogic
{
    public void SayMyName(string name)
    {
        Console.WriteLine(name);
    }
}
Listing 1. Przykład zastosowania PIAB.

Atrybut ValidationCallHandlerAttribute powoduje, iż przed uruchomieniem właściwego kodu metody uruchamiana jest walidacja parametrów. Atrybut NotNullValidator pochodzi już z Validation Application Block, a jego działanie jest chyba oczywiste.

Aby PIAB zadziałało potrzeba jeszcze opakować obiekt klasy Logic:

ILogic logic = PolicyInjection.Wrap<ILogic>(new Logic());

Oczywiście teraz po wywołaniu kodu:

logic.SayMyName(null);

dostaniemy wyjątek ArgumentValidationException, choć nigdzie jawnie nie dokonywaliśmy sprawdzania ani wyrzucania wyjątku. Wygodne, prawda?

...a po upływie miesiąca miodowego...

Zawsze po miłych początkach, nadchodzą pierwsze rozczarowania. Tak samo było, niestety, z moim "romansem" z PIAB. Zanim jednak przejdę do rzeczy, warto zajrzeć pod maskę i zobaczyć jak PIAB działa.

Otóż, podstawowym wymaganiem, by móc wstrzykiwać polityki do klasy jest aby:

  • implementowała ona interfejs przez który będziemy się do niej odwoływać lub
  • dziedziczyła po MarshalByRefObject.

Wymagania te wynikają stad, iż PIAB wewnętrznie wykorzystuje mechanizmy z .NET Remoting i magię CLR, która pozwala utworzyć transparentne proxy dla interfejsów lub właśnie typów dziedziczących po MarshalByRefObject. Owe transparentne proxy przekazują wywołanie do rzeczywistego proxy, którego zadaniem jest utworzenie ciągu odpowiednich call handlers (aspektów). Ostatnim na liście będzie zawsze kod dokonujący rzeczywistego wywołania docelowej metody. Poniższy diagram UML prezentuje jak to w uproszczeniu wygląda:

piab 
Diagram 1. Schematyczna struktura klas przy przechwytywaniu wywołań metod przez PIAB.

Jak widać ciąg wywołań będzie następujący:

  1. Użytkownik wywołuje metodę na transparent proxy.
  2. Dane o wywołaniu zostają zebrane (wartości argumentów, metoda, itp.) i trafiają do metody Invoke() klasy InterceptingRealProxy.
  3. Informacje o wywołaniu oraz delegat wywołujący następny call handler trafiają do pierwszego handlera. Handler może opakować swoją funkcjonalnością dalsze wywołanie.
  4. Wywołanie trafia stopniowo do wszystkich handlerów, aż na samym końcu pojawia się kod, który przez refleksję wywołuje docelową metodę. Wygląda on mniej więcej tak:
       1: try
       2: {
       3:     object returnValue = call.MethodBase.Invoke(/*..*/);
       4:     return /*...*/; //obiekt z informacją o powodzeniu i rezultatem
       5: }
       6: catch (TargetInvocationException ex)
       7: {
       8:     return /*..*/; //obiekt z informacją o wyjątku ex.InnerException
       9: }
    Listing 2. Wywołanie metody docelowej przez infrastrukturę PIAB.

Warto zwrócić uwagę na linijki 6-9, gdyż sygnalizują one mój pierwszy, na razie drobny problem.

Wyjątek wyrzucany dwa razy

Oto pierwsza niespodzianka - wywołując poniższy kod z Visual Studio:

public class Logic : MarshalByRefObject
{
    [ValidationCallHandler]
    public void SayMyName([NotNullValidator]string name)
    {
        Console.WriteLine(PrepareName(name));
    }

    private string PrepareName(string name)
    {
        throw new NotImplementedException();
    }
}

public class Program
{
    private static void Main(string[] argv)
    {
        var logic = PolicyInjection.Create<Logic>();
        logic.SayMyName("Kuba");
    }
}
Listing 3. Podróż wyjątku.

dostajemy następujący efekt:

image

Wygląda to, jakby wyjątek powstał gdzieś pomiędzy wywołaniem, a samym ciałem metody (co jest poniekąd prawdą). Spodziewalibyśmy się okienka dokładnie w miejscu wyrzucenia, czyli w metodzie PrepareName(). Na szczęście jest jeszcze stack trace, który zawiera cały stos wraz z miejscem postania wyjątku. Niby nic, ale każdy mniej doświadczony programista zgłosi się do nas, uznając, że jest to błąd w architekturze.

Z czego takie zachowanie wynika? Warto prześledzieć, co dzieje się z wyjątkiem:

  1. Wyjątek jest rzucany w metodzie PrepareName().
  2. Jako, że metoda SayMyName została wywołana za pomocą infrastruktury PIAB (poprzez refleksję - MethodBase.Invoke()), więc zostanie on opakowany przez TargetInvocationException. Ten zostanie przechwycony (listing 2, linijki 6-9), oryginalny wyjątek zostanie wyłuskany i przekazany jako element wyniku działania funkcji do wcześniejszych handlerów.
  3. Gdy wróci przez wszystkie handlery, to zostaje ponownie wyrzucony.

Stąd właśnie inne miejsce wyrzucenia wyjątku, wskazywane przez okienko "unhandled exception".

Prawdziwe problemy - środowisko hostowane, czyli partial trust...

Swojego czasu musiałem umieścić aplikację na jednym z większych polskich serwisów prowadzących hosting Windows. I tutaj zaczęły się prawdziwe schody. Problem polegał na tym, że w takich środowiskach poziom zabezpieczeń prawie nigdy nie jest ustawiony na Full Trust, a Enterprise Library nie zawsze jest dostępny z GAC (tak było w moim przypadku). Od razu zaznaczam, że problemem nie był fakt, że dopiero wersja 4.0 ma atrybut AllowPartiallyTrustedCallersAttribute (tzw. APTCA), a więc wersje wcześniejsze trzeba przekompilować bez podpisu cyfrowego, aby w ogóle się uruchomiły.

Problem polega na tym, że PIAB od środka wykorzystuje własne rzeczywiste proxy - InterceptingRealProxy, które dziedziczy po System.Runtime.Remoting.Proxies.RealProxy, która to klasa ma jako jeden z atrybutów:

[SecurityPermission(SecurityAction.InheritanceDemand, Flags=SecurityPermissionFlag.Infrastructure)]

Oznacza to, że aby dziedziczyć po RealProxy, assembly musi mieć uprawnienie do wpinania się do infrastruktury .NET Remoting. Jak nie trudno zgadnąć, we współdzielonym środowisku hostowanym zwykle takiego uprawnienia nie dostaniemy, a więc próba utworzenia proxy skończy się... SecurityException. Możemy poprosić administratora o nadanie tego uprawnienia, choć jest mało prawdopodobne, że się zgodzi.

Sytuacja na szczęście nie jest patowa - istnieje jeszcze jedno wyjście. PIAB pozwala na napisanie i podpięcie własnego Policy Injectora, który już z infrastruktury .NET Remoting korzystać nie musi. Na takie wyjście ja się zdecydowałem - postanowiłem napisać mechanizm, który będzie dynamicznie generował obiekty proxy implementujące wskazany interfejs i dalej wywołujące całą infrastrukturę Policy Injection (oczywiście takie podejście wyklucza wstrzykiwanie polityk do MarshalByRefObject). I po kilka dniach wspaniałej przygody z dynamicznym generowaniem kodu IL, z której wrażenia można porównać jedynie do czyszczenia zatkanych rur kanalizacyjnych, uzyskałem w miarę zadowalające rozwiązanie.

Pozwolę sobie nie przedstawiać dalszych szczegółów technicznych, gdyż nikt i tak by tego nie przeczytał, a najwięksi śmiałkowie pewnie zasnęliby w trakcie. Niemniej jednak, gdy moje rozwiązanie dopracuję (ma jeszcze kilka znanych mi błędów), to postaram się je opublikować, aby więcej osób mogło skorzystać z moich walk i nie musiało samemu przechodzić przez to piekiełko :).

Podsumowanie

Policy Injection Application Block to całkiem interesujące i wygodnie rozwiązanie, niemniej jednak, strategia przechwytywania wywołań wybrana przez twórców (infrastruktura .NET Remoting) może powodować problemy przy pewnych specyficznych sytuacjach, jak np. wdrażanie w środowisku o mniejszych uprawnieniach. Warto zdawać sobie z tego sprawę przy wyborze technologii wspierającej AOP podczas prac nad architekturą aplikacji.

opublikowano przez jakubin | 37 komentarzy

Transakcyjny mailing

W czym problem?

Wysłanie wiadomości e-mail w .NET jest dziecinnie proste:

MailMessage message = new MailMessage(
    "from@server.com", "to@server.com", "Temat", "Treść");
SmtpClient smtp = new SmtpClient();
smtp.Send(message);

Powyższy przykład jest bardzo krótki, choć i tak został napisany niezwykle rozwlekle - wersja zminimalizowana zajęłaby 1 linijkę (w obu przypadkach ustawienia serwera pocztowego znajdują się w pliku konfiguracyjnym). Właściwie ten kawałek kodu powinien wystarczyć nam do szczęścia, ale czasami zdarzają się bardziej skomplikowane przypadki.

Otóż, ostatnio pisałem kod, w którym kilka metod było wykonywanych w jednej transakcji. Jedna z metod wysyłała wiadomość e-mail z powiadomieniem o powodzeniu operacji. Problem pojawiał się, gdy wykonanie kolejnej się nie powiodło. Wówczas, transakcja nie była zatwierdzana - zmiany nie zostały zapisane do bazy, ale wiadomość o powodzeniu była wysyłana! Poniżej zamieściłem przykład takich metod:

public static void CreateUser(string email)
{
    //rejestracja konta użytkownika
    //...
    
    //wysłanie powiadomienia o rejestracji
    MailingHelper.Send(
        new MailMessage("admin@serwis.com",email,
            "Witaj na portalu!", 
            "Twoje konto zostało zarejestrowane."));
}

public static void GiveSiteAccess(string email)
{
    //nadanie uprawnień do serwisu WWW
    //...
}

Oraz ich wywołanie w ramach transakcji:

using (var ts = new TransactionScope())
{
    CreateUser("user@mail.com");
    GiveSiteAccess("user@mail.com");
    
    ts.Complete();
}

Jak widać, gdy wywołanie metody GiveSiteAccess się nie powiedzie, to i tak do użytkownika trafi wiadomość e-mail o sukcesie rejestracji, choć ta de facto nie nastąpiła.

Zadałem, więc sobie pytanie, czy nie da się jakoś podpiąć procesu wysyłania maili do bieżącej transakcji? Okazuje się, że tak i to bardzo łatwo. Wystarczy napisać własny menadżer zasobów i dołączyć go do transakcji.

Rozwiązanie - własny menadżer zasobów

Do informacji o bieżącej transakcji dostajemy się poprzez statyczną właściwość Current klasy System.Transactions.Transaction (w assembly System.Transactions.dll). Tam dostępne są metody, które pozwalają "dopisać się" do bieżącej transakcji:

  • EnlistVolatile - dołącza do transakcji menadżera zasobów ulotnych (zasoby ulotne to np. dane w pamięci; taki menadżer nie obsługuje odtwarzania stanu po błędzie),
  • EnlistPromotableSinglePhase - dołącza do transakcji menadżera zasobów, który obsługuje tryb promotable single phase enlistment,
  • EnlistDurable - dołącza do transakcji menadżera zasobów trwałych (zasoby trwałe to np. dane na dysku twardym; taki menadżer posiada obsługę odtwarzania stanu po błędzie).

Nas będzie interesować pierwsza metoda, gdyż wiadomość e-mail to zasób ulotny. Menadżer zasobów ulotnych w najprostszej postaci to klasa implementująca interfejs IEnlistmentNotification. Nasz menadżer będzie wyglądał następująco:

public class MailSender : IEnlistmentNotification
{
    private readonly MailMessage message;

    public MailSender(MailMessage message)
    {
        this.message = message;
    }

    #region IEnlistmentNotification Members

    public void Commit(Enlistment enlistment)
    {
        //powoduje faktyczne wysłanie wiadomości
        MailingHelper.SendImmediately(message);
        enlistment.Done(); //menadżer zgłasza zakończenie swojej pracy
    }

    public void InDoubt(Enlistment enlistment)
    {
        enlistment.Done();
    }

    public void Prepare(PreparingEnlistment preparingEnlistment)
    {
        //menadżer potwierdza zakończenie przygotowań
        preparingEnlistment.Prepared(); 
    }

    public void Rollback(Enlistment enlistment)
    {
        enlistment.Done(); //menadżer potwierdza wycofanie transakcji
    }

    #endregion
}

Metody interfejsu IEnlistmentNotification będą wykonywane zgodnie z protokołem dwufazowego zatwierdzania (two-phase commit). W dużym skrócie, proces ten składa się z dwóch faz: fazy przygotowania i fazy zatwierdzania. Menadżer transakcji najpierw żąda przygotowania się menadżerów zasobów - metoda Prepare. Każdy menadżer może zgłosić gotowość (wywołanie preparingEnlistment.Prepared()) lub wymusić wycofanie transakcji (wywołanie preparingEnlistment.ForceRollback()). Jeżeli wszystkie menadżery zgłoszą gotować, to transakcja jest zatwierdzana - metoda Commit. W przeciwnym razie wszystkie menadżery dostają polecenie wycofania transakcji - metoda Rollback. Natomiast, gdy menadżer transakcji straci łączność z którymś z menadżerów zasobów, to wywoływana jest metoda InDoubt.

Dołączenie menadżera zasobów do transakcji odbywa się następująco:

Transaction.Current.EnlistVolatile(new MailSender(message), EnlistmentOptions.None);

Ponieważ cała magia jest już znana, oto kod klasy pomocniczej MailingHelper:

public static class MailingHelper
{
    public static void Send(MailMessage message)
    {
        if (Transaction.Current != null)
        {
            Transaction.Current.EnlistVolatile(
                new MailSender(message), EnlistmentOptions.None);
        }
        else
            SendImmediately(message);
    }

    public static void SendImmediately(MailMessage message)
    {
        var smtp = new SmtpClient();
        smtp.EnableSsl = true;
        smtp.Send(message);
    }
}

Jak widać, metoda SendImmediately wysyła wiadomość natychmiast, podczas gdy metoda Send podpina wysłanie wiadomości pod bieżącą transakcję (jeżeli taka istnieje; w przeciwnym razie wysyła maila od razu). Efekt jest taki, jaki chcieliśmy osiągnąć - poczta jest wysyłana dopiero w momencie zatwierdzenia transakcji.

Kilka słów wyjaśnienia

Przedstawione rozwiązanie, wbrew tytułowi, nie jest receptą na zbudowanie w pełni transakcyjnego mailingu. Tutaj tylko osiągamy efekt opóźnienia wysłania wiadomości e-mail do momentu zatwierdzenia transakcji. Ponadto, kod został uproszczony dla zwiększenia czytelności.

Bardziej dociekliwych zapraszam do:

  1. Dokumentacja klasy Transaction.
  2. Dokumentacja klasy TransactionScope.
  3. Dokumentacja interfejsu IEnlistmentNotification.
  4. Artykuł o implementacji własnego menadżera zasobów.
opublikowano przez jakubin | 12 komentarzy
Filed under:
Więcej wypowiedzi Następna strona »