12/01/2009

Czemu zdarzenia nie działają ??? (część 2)

Home

W poście Czemu zdarzenia nie działają??? opisałem błąd, który doprowadza do sytuacji, w której nie otrzymujemy zdarzeń od dynamicznie tworzonych kontrolek. Jego rozwiązanie nie jest specjalnie skomplikowane i dla przypomnienia przytoczę je tutaj:

W przypadku dynamicznego tworzenia kontrolek, czy to bezpośrednio czy to przy okazji użycia kontrolek data bound zawsze należy pamiętać aby dynamiczne kontrolki były tworzone nie tylko przy inicjalnym odwołaniu do strony ale również przy kolejnych.

W tym poście chciałbym jeszcze zwrócić uwagę gdzie powinny być tworzone dynamiczne kontrolki. Najlepszym miejsce jest zdarzenie PreInit strony. Powodów jest kilka, a najważniejsze to:
  • Zdarzenie PreInit jest odpalane najwcześniej w cyklu życia strony. Jeśli stworzymy kontrolki w tym miejscu będą one dostępne w każdym kolejnym zdarzeniu - kontrolki dynamiczne będą zachowywały się tak samo jak kontrolki statycznie osadzone na stronie. To jest pierwszy i najważniejszy powód, a wszystkie wymienione dalej stanowią jego rozwinięcie.
  • Zdarzenie PreInit to jedyne miejsce, w którym możemy dynamicznie ustawić właściwość Theme. Jeśli stworzymy nasze kontrolki później to wartość tej właściwości nie będzie miała wpływu na sposób ich prezentacji.
  • Będziemy mieli zagwarantowane, że właściwości kontrolek zostaną odtworzone na podstawie ViewState.
  • Będziemy mieli pewność, że jeżeli dla dynamicznie stworzonej kontrolki ma zostać wygenerowane zdarzenie to zostanie. Innymi słowy będziemy mieli pewność, że w punkcie, w którym maszyneria ASP.NET określa dla jakiej kontrolki wywołać jakie zdarzenie, ta kontrolka będzie istnieć.
  • Takie są zalecenia :)

06/01/2009

ASP.NET Caching

Home

W poście tym chciałbym napisać kilka słów o unieważnianiu zawartości cache'a w aplikacjach ASP.NET na podstawie aktualizacji danych przechowywanych w bazie danych. Post ten nie jest jednak przewodnikiem na temat cache'owania w aplikacjach ASP.NET, opisuję w nim kilka moim zdaniem ważnych elementów. Post ten można traktować jako punkt wyjścia do dalszej nauki.

Idea jest bardzo prosta. W celu przyspieszenia aplikacji umieszczamy w cache'u jakieś dane np.: wynik zapytania czy też wyrenderowaną stronę (przy pomocy dyrektywy @OutputCache). Chcielibyśmy jednak aby w przypadku jeśli dane, na podstawie których zostało wykonane zapytanie, ulegną zmianie, ponownie wykonać zapytanie zamiast wczytywać jego wynik z cache'a. W aplikacjach ASP.NET możemy wybierać z dwóch możliwości. Jedna to obiekt klasy Cache dostępny przez właściwość strony o takiej samej nazwie. Dane umieszczone w tym pojemniku mogą zostać usunięte po upływie określonego czasu, kiedy zmianie uległ wskazy plik czy też (co interesuje nas w tej chwili) kiedy dane w bazie danych zostały zmienione. Po drugie mamy dostęp do cache'a przechowującego wyrenderowane strony czy też ich fragmenty. Z tego cache'a nie korzystamy bezpośrednio ale przy pomocy dyrektywy @OutputCache. W tym przypadku również możemy wybierać pomiędzy kilkoma wariantami unieważniania zawartości cache'a.

Przejdźmy do konkretów. W obu przypadkach aby powiązać dane w cache'u z danymi w bazie danych używamy klasy SqlCacheDependency. W przypadku cache'a dostępnego z poziomu strony musimy jawnie utworzyć instancję tej klasy. Przykład użycia znajdziemy tutaj. Natomiast w przypadku dyrektywy @OutputCache musimy użyć atrybutu SqlDependency.

Istotne jest co dzieje się pod spodem. SqlCacheDependency używa klasy SqlDependency (nazwa klasy jest taka sama jak nazwa wspomnianego wcześniej atrybutu). SqlDependency to z kolei wysoko poziomowa nakładka na inną klasę SqlNotificationRequest. Ważne jest to, że SqlDependency i SqlNotificationRequest współpracują tylko z bazą danych Sql Server 2005 i nowszymi, które dostarczają usługi powiadomień na poziomie wierszy - query notifications. To znaczy, że możemy wskazać wierszy, w przypadku zmiany których dane z cache'a zostaną usunięte. W przypadku starszych baz danych (lub nowych jeśli nie odpowiada nam query notifications) SqlCacheDependency wykorzystuje mechanizm monitorowania całych tabel zamiast wskazanej grupy wierszy. Jest to ogólnie rozwiązanie mniej wydajne.

Jeszcze parę słów o @OutputCache. Jak napisałem wcześniej aby użyć unieważniania na podstawie bazy danych należy wysterować atrybut SqlDependency. Składnia tego atrybutu jest następująca:
<%@OutputCache
...
SqlDependency="database/table name pair | CommandNotification"
...
%>
W przypadku podania pary nazwa bazy danych i nazwa tabeli zostanie użyte monitorowanie na poziomie całych tabel. W przypadku podania wartości CommandNotification zostanie użyte powiadamianie na poziomie wierszy, a dokładniej ASP.NET użyje wszystkich komend użytych w kontekście danej strony do utworzenia zależności pomiędzy danymi w cache'u, a danymi w bazie danych.

W tym momencie należy jeszcze zaznaczyć, że o ile klasa SqlCacheDependency jest właściwa dla aplikacji ASP.NET to SqlDependency i SqlNotificationRequest mogą być użyte również w innych przypadkach. W ogólności użycie SqlNotificationRequest jest raczej trudne i pracochłonne. W większości przypadków używa się SqlDependency. Przykład użycia tej klasy można znaleźć tutaj tutaj.

24/12/2008

Życzenia

Home

Życzę wszystkim aby spędzili Święta w taki sposób w jaki sobie wymarzą i z tymi ludźmi, przy których będą czuli się najlepiej.

Serdecznie pozdrawiam
Michał Komorowski

23/12/2008

Expression Studio

Home

W zeszłym tygodniu (dokładnie 17 grudnia) miałem przyjemność uczestniczyć w spotkaniu pod tytułem Nowoczesny interfejs użytkownika, oraz aplikacje web’owe - technologie i narzędzia Microsoft zorganizowanym przez firmę Microsoft. Uczestnictwo w tym spotkaniu naprawdę było przyjemnością, szczególnie jeżeli porównam je do Microsoft Technology Summit 2008, które według mnie było organizacyjną porażką, a nawet określił bym je pogardliwym mianem tzw. masówki. Spotkanie zorganizowane było w siedziby firmy Microsoft w Warszawie, a poprowadził je Artur Żarski (Developer Evangelist). W tej mini konferencji uczestniczyło około 60 osób. Prelekcja została przeprowadzono bardzo sprawnie i generalnie była ciekawa. Prelegent omówił pięć narzędzi wchodzących w skład Expression Studio:
  • Expression Media Encoder - obrabianie filmów
  • Expression Media - zarządzanie multimediami
  • Expression Web - projektowanie stron WWW
  • Expression Blend - projektowanie interfejsu użytkownika
  • Expression Designer - tworzenie grafiki wektorowej i rysunków
Dokładną charakterystykę i opis tych produktów znajdziemy na tej stronie.

Dla mnie najbardziej interesujące była cześć poświęcona narzędziom Expression Web oraz Expression Blend dlatego poświęcę im jeszcze kilka słów. Na początek należy stwierdzić, że te dwa narzędzia (zresztą pozostałe trzy podobnie) nie są przeznaczone dla programistów, a przynajmniej nie powinny być używane przez programistów. Nie powinny w tym sensie, że zalecane jest aby interfejsy użytkownika były projektowane przez wyznaczonych do tego projektantów. Oczywiście wiem jakie są realia. Napisałem tutaj o sytuacji idealnej.

Ogólnie zaleca się aby osoby/zespoły pracujące na interfejsem użytkownika były niezależne (odseparowane) względem osób/zespołów tworzących właściwą aplikację. Po pierwsze oba zadania wymagają zupełnie innych umiejętności. Po drugie dzięki takiemu podejściu osiągamy stan, w której projektanci interfejsu użytkownika nie przeszkadzają programistą i vice versa. W szczególności unikamy problemy pod tytułem: Kto zmienił nazwę tej kontrolki? Podobnie jest przy tworzeniu gier komputerowych. Modele postaci, bitmapy tworzone są przez wyspecjalizowanych grafików. Efekt ich pracy otrzymują programiści, którzy ożywiają modele, wyświetlają bitmapy, dodają efekty specjalne: oświetlenie, cienie...

Powstaje pytanie co należy stworzyć najpierw. Moim zdaniem należy pamiętać, że interfejs użytkownika to nie tylko wygląd kontrolek, ikony, kolory itd. ale również ergonomia, odpowiednie rozłożenie kontrolek i organizacja przepływu sterowania. Wydaje się, że elementy te powinny zostać zdefiniowane w pierwszej kolejności tak aby programiści znali je już na samym początku pracy. Unikamy potrzeby przebudowy kodu na późniejszym etapie pracy, a w szczególności unikamy sytuacji, w której nad tym samym elementem interfejsu pracuje równocześnie programista i projektant UI. Dlatego jestem zwolennikiem podejścia, w którym interfejs, a już napewno jego projekt/opis, powstaje najpierw.

Jeśli chodzi o Expression Web to godny zauważenia jest fakt, że Microsoft zauważył PHP i dostarczył narzędzia wspierającego ten język. Zaskoczył mnie natomiast fakt, że w przypadku aplikacji ASP.NET nie jest wspierany model code behind!.

Trochę głupio przyznać ale zaskoczyła mnie również informacja, że Expression Blend to aplikacja komercyjna (czytaj płatna). Do tej pory korzystałem bowiem z darmowej wersji community technology preview i miałem nadzieję, że ten stan utrzyma się w wersji finalnej. Szkoda, bo aplikacja ma ogromne możliwość i umożliwia wyczarowanie naprawdę fajnych efektów przy użyciu XAML'a - niektórych rzeczy nie da się po prostu zrobić czysto programistycznie, no chyba, że ktoś potrafi wyobrazić sobie wszystko w głowie.

15/12/2008

Domknięcie - Jak to działa?

Home

Domknięcie (ang. Closure) to cecha języków programowania, która pozwala funkcji odwołać się to zmiennych zdefiniowanych poza jej ciałem. Domknięciem nazywa się również obiekt (byt) wiążący funkcję z środowiskiem jej wykonania. Oczywiście chodzi o zmienne inne niż globalne. Takie powiązane zmienne w języku angielskim określane są mianem bound variables. Przykład domknięcia został przedstawiony poniżej. Proponuję przyjrzeć się temu kodowi i odpowiedzieć co zostanie wypisane na ekran. W dalszej części posta będę używał terminu metoda zamiast funkcja, który bardziej pasuje do obiektowego języka jakim jest C#.
delegate void Fun();
...
List array = new List();
for (int i = 0; i < 5; ++i)
{
  array.Add(delegate() { Console.WriteLine(i); });
}

for (int i = 0; i < 5; ++i)
{
  array[i]();
}
Implementacja domknięcia zależy od konkretnego języka programowania. Mnie oczywiście zaciekawiło jak to zostało zrealizowane w C# i tym zajmę sie w tym poście. W rozwiązaniu zagadki pomógł oczywiście reflektor Lutz Roeder’s Reflector, o którym wspominałem już wcześniej.

Implementacja domknięcia w języku C# bazuje na automatycznie generowanej przez kompilator klasie. Klasa ta zawiera kod metody oraz powiązane z nią zmienne. Dla naszego przykładu klasa ta będzie wyglądać jak poniżej:
[CompilerGenerated]
private sealed class DisplayClass
{
  public int i;

  public void b_0()
  {
    Console.WriteLine(this.i);
  }
}
Kod zmieniłem tak aby był bardziej czytelny. Jak widać wygenerowana klasa jest trywialna. Zawiera jedną metodę, której zawartości odpowiada zawartości zdefiniowanej przez nas anonimowej metody oraz jedno publiczne pole, które stanowi nic innego jak powiązaną zmienną. No dobrze, ale kto korzysta z wygenerowanej klasy i kto ustawia wartość publicznego pola i. Odpowiedź nasuwa się sama - oczywiście kompilator. A jak? Zostało to schematycznie przedstawione poniżej:
DisplayClass d = new DisplayClass();

List array = new List();
for (d.i = 0; d.i < 5; ++d.i)
{
  array.Add(d.b_0);
}

for (int i = 0; i < 5; ++i)
{
  array[i]();
}
Kod ten może trochę dziwić. Od razu nasuwa się pytanie, czemu utworzono tylko jedną instancję automatycznie wygenerowanej klasy? Wszystko się jednak zgadza. Domknięcie działa w ten sposób, że anonimowa metoda ma dostęp do wartości zmiennej powiązanej z czasu jej wykonywania, a nie z czasu kiedy została utworzona. Nie trudno zauważyć, że w momencie wywoływania metody wartość zmiennej i wynosi 5. Odpowiedź na postawione przeze mnie wcześniej pytanie brzmi, więc: Na ekran zostanie wypisany ciąg: 5 5 5 5 5. Poniżej podaję sposób w jaki osiągnąć bardziej intuicyjny efekt wypisania na ekran ciągu: 0 1 2 3 4. Należy tylko zmodyfikować pierwszą pętlę:
for (int i = 0; i < 5; ++i)
{
  int j = i;
  array.Add(delegate() { Console.WriteLine(j); });
}
Dla takiego przypadku kod wygenerowany przez kompilator będzie już inny. W szczególności zostanie utworzonych 5 instancji klasy DisplayClass, a nie jedna jak w poprzednim wypadku.

Przykład omówiony przeze mnie jest bardzo prosty. W bardziej skomplikowanych przypadkach sprawa nie jest już tak prosta ale koncepcja implementacji domknięcia pozostaje taka sama.