View State dla TextBox i innych kontrolek implementujących IPostBackDataHandler

by Miłosz Orzeł 8. January 2012 11:20

Czytając oficjalny trening kit do egzaminu 70-515 natrafiłem na taki tekst: "With view state, data is stored within controls on a page. For example, if a user types an address into a TextBox and view state is enabled, the address will remain in the TextBox between requests.". Skoro takie zdania padają w zalecanym podręczniku, nic dziwnego, że łatwo pogubić się w tym jak ASP.NET Web Forms próbuje radzić sobie z naturalną dla HTTP bezstanowością... ;)

Kontrolka TextBox ze strony ASPX:

<asp:TextBox ID="TextBox1" runat="server"></asp:TextBox>

Jest na stronie HTML renderowana jako tag input:

<input name="TextBox1" type="text" id="TextBox1" />

Skoro tak, to dla zachowania wartości kontrolki między requestami nie ma konieczności przechowywania wartości pola TextBox1 w ukrytym polu __VIEWSTATE. By się o tym przekonać stwórz prostą stronę zawierajacą kontrolki TextBox i Button:

...

<body>
    <form id="form1" runat="server">
    	<asp:TextBox ID="TextBox1" runat="server"></asp:TextBox>
    	<asp:Button ID="Button1" runat="server" Text="Button" onclick="Button1_Click" /
    </form>
</body>
</html>

i dodaj handler dla zdarzenia Click przycisku, którego jedynym zadaniem jest rozwijanie tekstu znajdującego się w kontolce TextBox1:

protected void Button1_Click(object sender, EventArgs e)
{
    TextBox1.Text += "X";
}

Następnie uruchom stronę i aktywuj narzędzie pozwalające na monitorowania komunikacji pomiędzy przeglądarką a serwerem. Interesuje nas badanie danych formularza przesyłanych na serwer przy postbacku... Jeśli używasz IE polecam program Fiddler, pod Firefoxem skorzystaj z dodatku Firebug. Możesz też użyć wbudowanego w ASP.NET mechanizmu Trace - w tym celu dopisz Trace="true" do dyrektywy @Page. Ja przeprowadzę test przy użyciu narzędzi deweloperskich dostarczonych wraz z przeglądarką Chrome (zakładka "Network").

Na poniższym screenie widać jakie dane formularza (request HTTP POST) zostały wysłane przy pierwszym naciśnięciu przycisku:

Dane formularza przy pierwszym postbacku

Tutaj widać dane z drugiego postbacka:

Dane formularza przy drugim postbacku

Jeśli porównasz dane z pierwszego i drugiego żądania, zobaczysz, że zmiana wartości TextBox1.Text nie wpływa na wartość pola __VIEWSTATE. Rozszerzanie tego pola byłoby marnotrastwem zasobów łącza skoro tekst jest wysyłany na serwer w osobnym polu o nazwie TextBox1.

Klasa System.Web.UI.WebControls.TextBox jest jedną z kilku klas implementującyh interfejs IPostBackDataHandler. Interfejs ten wymaga istnienia metody LoadPostData. Po zakończeniu incjalizacji strony (ale przed zdarzeniem Load), wykonywane jest ładowanie danych z View State (LoadViewState) a następnie (o ile kontrolka implementuje IPostBackDataHandler) z danych formulrza (LoadPostData). Właściwość Text kontolki typu TextBox może być zatem ustawiona nawet jeśli mechanizm View State jest wyłączony (poprzez ustawienie EnableViewState="false").

Czy nie można więc zupełnie zrezygnować z mechanizmu View State dla TextBoxa i podobnych kontrolek?

Nie. View State przydaje się np. wtedy gdy obsługiwane jest zdarzenie TextChanged (dla porównania wartości aktualnej i poprzedniej). Może mieć też zastosowanie gdy ustawiana jest inna właściwość kontrolki niż ta utożsamiana z wartością pola (np. ForeColor).

Wykrycie załadowania iframe stworzonego w Ext JS

by Miłosz Orzeł 5. November 2011 17:53

Przypuśćmy, że zachodzi potrzeba wykonania jakiegoś fragmentu kodu w momencie załadowania zawartości iframe. W przy- padku gdy iframe jest utworzony statycznie w kodzie HTML strony, sprawa jest bardzo prosta. Wystarczy podłączyć funkcję JavaScript pod zdarzenie load:

<iframe src="http://wikipedia.org" width="600" height="400" onload="someFunction();" ></iframe>

Uwaga: zdarzenie load (onload) zostanie wywołane gdy cała zawartość dokumentu zostanie załadowana (w tym jego elementy zewnętrzne takie jak obrazki). Jeśli trzeba zadziałać wcześniej, tzn. w chwili gdy DOM jest gotowy, należy skorzystać z innych metod...

A co w przypadku iframe’a stworzonego w kodzie Ext JS?

Prostym sposobem na utworzenie go jest użycie Ext.BoxComponent z odpowiednio ustawioną wartością autoEl. Daje to możliwość łatwego wpisania iframe’a w layout Ext JS (np. jako składowej Ext.Window) bez rozszerzania drzewa dokumentu o zbędne elementy.

var iframeContainer = new Ext.BoxComponent({
    autoEl: {
        tag: 'iframe',
        frameborder: '0',
        src: 'http://wikipedia.org'
    },
    listeners: {
        afterrender: function () {
            console.log('rendered');

            this.getEl().on('load', function () {
                console.log('loaded');
            });
        }
    }
});


W powyższym kodzie (Ext JS 3.2.1) bardzo istotny jest moment, w którym pod zdarzenie load iframe’a podpinany jest handler. Można to zrobić jedynie po tym gdy kontrolka (BoxComponent) zostanie wyrenderowana. Jeśli zrobi się to wcześniej, wówczas wywołanie getEl() zwróci undefined i kod nie zadziała. Przed renderowaniem kontrolka Ext JS to jedynie obiekt JavaScript, dla którego nie istnieją żadne elementy w drzewie dokumentu. Poniżej znajdują się dwa screeny prezentujące fragmenty kodu HTML utworzonego przez Ext.Window, w którym jedynym elementem na liście items był BoxComponent tworzący tag iframe...

beforerender:

DOM beforerender

afterrender:

DOM afterrender


Widać wyraźnie, że zbyt wczesne podpinanie się pod load nie ma prawa zadziałać bo nie można przecież nasłuchiwać zdarzeń na czymś co nie isntnieje.

Powyższe zrzuty pochodzą z okna Elements narzędzia Chrome Developers Tools. Szybkim sposobem by pokazać to narzędzie (oczywiście w przeglądarce Googla) jest naciśnięcie Ctrl+Shift+I. Fajną funkcją CDT jest możliwość wylistowania zdarzeń nasłuchiwanych na określonym elemencie DOM. By zobaczyć listę trzeba zaznaczyć element DOM i w menu po prawej stronie wybrać zakładkę Event Listeners. Na screenie poniżej widać, że iframe faktycznie posiada obsługę zdarzenia load:

CDT Event Listeners

BadImageFormatException, x86 i x64

by Miłosz Orzeł 12. September 2011 22:53

Czy natrafiłeś kiedyś na wyjątek typu BadImageFormatException lub komunikat "An attempt was made to load a program with an incorrect format" ("Próbowano załadować program w niepoprawnym formacie")?

Jeśli tak to być może program, który próbowałeś uruchomić nie został skompilowany z użyciem opcji /platform:x86. Zastanawiasz się pewnie dlaczego podczas programowania w C# powinieneś przejmować się tym na jakiej platformie (x86/x64) kod zostanie wykonany. Cóż, w większości przypadków nie musisz o tym myśleć. Jeśli nie używasz bloków unsafe ani nie importujesz natywnych modułów problemu nie ma, bowiem Twój kod C# zostaje przetłumaczony do kodu pośredniego (CIL), który przed wykonaniem zostaje skompilowany (JIT) na postać odpowiednią dla platformy docelowej. Ok, ale...

Wyobraź sobie, że używasz w swojej aplikacji funkcji importowanej z 32 bitowej DLL. Gdy uruchomisz program na 32 bitowym systemie wszystko działa jak należy. Niestety na maszynie x64 otrzymujesz wspominany wyjątek BadImageFormatException. Dlaczego? Jeśli assembly importujące DLL zostało skompilowane z opcją /platform:anycpu to do jego uruchomienia na systemie 64 bitowym została użyta 64 bitowa wersja CLR. Próba załadowania 32 bitowej DLL z aplikacji działającej w procesie 64 bitowym nie powiedzie się. Gdyby do kompilacji została użyta opcja /platform:x86 wówczas na 64 bitowym systemie operacyjnym program zostałby uruchomiony w 32 bitowej wersji CLR (z użyciem WoW64: Windows 32-bit on Windows 64-bit).

Platformę docelową (przełącznik /platform kompilatora C#) można w Visual Studio 2010 ustawić w oknie “Properties” na zakładce “Build”. By tam trafić kliknij prawym klawiszem w plik projektu w Solution Explorer i wybierz „Properties” lub użyj menu głównego „Project | <nazwa projektu> Properties…”.

Ustawienie platformy docelowej w Visual Studio 2010. Kliknij aby powiększyć...

Microsoft stworzył przydatne narzędzie wiersza poleceń o nazwie CorFlags, które służy między innymi do podglądu lub ustawiania platformy docelowej. Dostęp do tego narzędzia można uzyskać korzystając z Visual Studio Command Prompt albo przez znalezienie go bezpośrednio na dysku (u mnie jesto ono pod C:\Program Files\Microsoft.NET\SDK\v2.0\Bin\CorFlags.exe)

Poniżej znajduje się kilka przykładów tego co możesz zobaczyć po sprawdzeniu plików EXE stworzonych z różnymi wartościami opcji /platform kompilatora (do sprawdzenia pliku służy polecenie: CorFlags nazwa.pliku):

anycpu x86 x64
Version   : v4.0.30319
CLR Header: 2.5
PE        : PE32
CorFlags  : 1
ILONLY    : 1
32BIT     : 0
Signed    : 0
Version   : v4.0.30319
CLR Header: 2.5
PE        : PE32
CorFlags  : 3
ILONLY    : 1
32BIT     : 1
Signed    : 0
Version   : v4.0.30319
CLR Header: 2.5
PE        : PE32+
CorFlags  : 1
ILONLY    : 1
32BIT     : 0
Signed    : 0

W kontekście tego posta istotne są 2 rzędy z wyniku zwróconego przez CorFlags: PE i 32BIT.

  • PE: PE32 oznacza, że plik może być wykonany w środowisku x86 i x64
  • PE: PE32+ oznacza, że plik może być wykonany jedynie w środowisku 64 bitowym
  • 32BIT: 1 oznacza, że program musi być wykonany w środowisku x86

Zrozumienie znaczenia 32BIT: 1 jest naprawdę istotne jeśli chcesz uniknąć problemów z importowanie 32 bitowch DLL na 64 bitowej wersji Windows. Jeśli flaga 32BIT jest ustawiona i uruchomisz plik PE32 na x64 wówczas Twoja aplikacja zostanie uruchomiona w środowisku 32 bitowym (z użyciem WoW), dzięki czemu zaistnieje możliwość zaimportowania 32 bitowej DLL. Jeśli flaga 32BIT nie jest ustawiona, aplikacja uruchomi się w procesie 64 bitowym – co spowoduje problem z załadowaniem biblioteki.

Dzięki CorFlags można w łatwy sposób zmodyfikować wartość flagi 32BIT. Do jej ustawienia służy przełącznik /32BIT+

CorFlags file.exe /32BIT+

A do jej usuwania /32BIT-

CorFlags file.exe /32BIT-

Tak więc nawet jeśli nie masz możliwości przekompilowania problematycznego kodu z odpowiednią opcją /platform nadal możesz użyć 32 bitowej DLL w 64 bitowej wersji systemu Windows :)

Dlaczego użycie GetPixel i SetPixel jest tak bardzo nieefektywne?

by Miłosz Orzeł 5. April 2011 23:28

Klasa Bitmap dostarcza dwie proste metody: GetPixel i SetPixel służące odpowiednio do pobierania koloru punktu obrazu (jako struktury Color) oraz ustawienia punktu obrazu. Poniższy kod ilustruje sposób pobrania/ustawiania wszystkich pikseli bitmapy:

private void GetSetPixel(Bitmap image) {
   for (int x = 0; x < image.Width; x++) {
      for (int y = 0; y < image.Height; y++) {
         Color pixel = image.GetPixel(x, y);
         image.SetPixel(x, y, Color.Black);
      }
   } 
}

Jak widać przeglądnięcie i modyfikacja pikseli jest niezwykle prosta. Niestety za prostotą kodu kryje się poważna pułapka wydajnościowa. O ile dla niewielkiej ilości odwołań do punktów obrazu, prędkość z jaką działają metody GetPixel i SetPixel jest zadowalająca, o tyle dla większych rozmiarów obrazu jest ona zdecydowanie za mała. Za dowód może posłużyć wykres z wynikami 10 testów*, które polegały na 10-krotnym wywołaniu w/w metody GetSetPixel na obrazach 100x100 i 1000x1000 pikseli:

Wyniki testów prędkości operacji na pikselach obrazu z użyciem metod GetPixel i SetPixel klasy Bitmap.

Średni czas testu dla obrazu o wymiarach 100 na 100 pikseli wyniósł 543 milisekundy. Taka wydajność jest możliwa do zaakceptowania o ile przetwarzanie obrazu nie będzie wykonywane często. Problem wydajnościowy jest natomiast jasno widoczny przy próbie obsługi obrazu o rozmiarach 1000 na 1000 pikseli. Wykonanie testu zabiera w tym przypadku średnio ponad 41 sekund – ponad 4 sek. na jedno wywołanie GetSetPixel (sic!) .


Dlaczego tak wolno?

Niska wydajność spowodowana jest tym, że dostęp do piksela nie jest prostym odwołaniem do obszaru pamięci. Każde pobranie lub ustawienie koloru wiąże się z wywołaniem metody .NET Framework, będącej oprawą dla natywnej funkcji zawartej w bibliotece gdiplus.dll. Wywołanie to następuje za pomocą mechanizmu P/Invoke (Platform Invocation), który służy do komunikacji kodu zarządzanego z API niezarządzanym (API z poza .NET Framework). Tak więc np. dla bitmapy o rozmiarze 1000x1000 pikseli dojdzie do miliona wywołań metody GetPixel, która prócz walidacji parametrów korzysta z funkcji natywnej GdipBitmapGetPixel. Metoda z API GDI+ musi z kolei przed zwróceniem informacji o kolorze wykonać takie operację jak np. wyliczenie położenia bajtów odpowiedzialnych za opis szukanego piksela... Sytuacja analogiczna zachodzi w przypadku metody SetPixel.

Spójrz na poniższy kod metody Bitmap.GetPixel uzyskany dzięki .NET Reflector (System.Drawing.dll, .NET Framework 2.0):

public Color GetPixel(int x, int y) {
   int argb = 0;
   if ((x < 0) || (x >= base.Width)) {
      throw new ArgumentOutOfRangeException(“x”, SR.GetString(“ValidRangeX”));
   }
   if ((y < 0) || (y >= base.Height)) {
      throw new ArgumentOutOfRangeException(“y”, SR.GetString(“ValidRangeY”));
   }
   
   int status = SafeNativeMethods.Gdip.GdipBitmapGetPixel(new HandleRef(this, base.nativeImage), x, y, out argb);
   if (status != 0) {
      throw SafeNativeMethods.Gdip.StatusException(status);
   }
   return Color.FromArgb(argb);
}

A oto import funkcji z GDI+:

[DllImport(“gdiplus.dll”, CharSet=CharSet.Unicode, SetLastError=true, 
ExactSpelling=true)]
internal static extern int GdipBitmapGetPixel(HandleRef bitmap, int x, int y, out int argb);

Teraz już wiesz dlaczego masowe użycie Get/SetPixel jest takie powolne. Na szczęście istnieją inne (dużo szybsze) sposoby obsługi pikseli z poziomu .NET. Przy pewnym wysiłku można napisać kod, który szybciej obsłuży obraz megapikselowy niż prymitywna metoda poradzi sobie z bitmapą 100x100!. Ale o tym, gdy znajdę nieco czasu... ;)

* Testowałem na takim laptopie: HP Pavilion dv5, procesor AMD Turion X2 Dual-Core Mobile RM-70, 3 GB RAM, Vista Home Premium

IBM.WMQ.MQMessage.ReadString i EndOfStreamException

by Miłosz Orzeł 18. April 2010 23:39

Ostatnio podczas przeróbki programu do komunikacji z WebSphere MQ (v6.0.2.7) zacząłem znajdować w logach wyjątek typu EndOfStreamException. Jako, że kod adaptera był dość złożony chwilę zajęło zanim znalazłem banalną przyczynę problemów ;)

System.IO.EndOfStreamException: Nie można odczytać danych spoza końca
strumienia.
   w System.IO.__Error.EndOfFile()
   w System.IO.BinaryReader.ReadByte()
   w System.IO.BinaryReader.Read7BitEncodedInt()
   w System.IO.BinaryReader.ReadString()
   w IBM.WMQ.MQMessage.ReadString(Int32 length)

Błąd był zgłaszany dlatego, że czasem w dwóch różnych miejscach występowało wywołanie metody ReadString na tym samym obiekcie MQMessage:

string text = message.ReadString(message.MessageLength);

By pozbyć się kłopotu wystarczy dodać jedną linię kodu:

string text = message.ReadString(message.MessageLength);
message.Seek(0);

Skąd problem?
ReadString to metoda odczytująca strumień bajtów i konwertująca go do stringa*. Po poprawnym odczycie całej treści komunikatu znacznik bieżącej pozycji w strumieniu pozostawał na jego końcu, więc następne wywołanie ReadString musiało skończyć się wyjątkiem EndOfStreamException. Dlaczego musiało? ReadString (IBM.WMQ.MQMessage) korzysta w środku z danych przechowywanych w obiekcie typu MemoryStream. Podczas pobierania tekstu, w zależności od właściwości DataLength komunikatu może być wywoływana metoda ReadString z klasy .NET Framework System.IO.BinaryReader. By odczytać tekst musi ona najpierw pobrać jego zakodowaną długość - do tego służy metoda Read7BitEncodedInt widoczna na śladzie stosu. Korzysta ona z kolei z ReadByte, która po natrafieniu na koniec strumienia rzuca omawiany wyjątek.

* Konwersja zachodzi z użyciem właściwości CharacterSet komunikatu (CCSID).

Dlaczego stringi są niezmienne i co z tego wynika?

by Miłosz Orzeł 26. January 2010 23:41

Niestety zajmiemy się mniej namacalną implementacją stringów ;)

Typ string (System.String) służy do reprezentacji łańcuchów tekstowych w postaci sekwencji wartości typu char (System.Char) określających znaki Unicode (zakodowane w UTF-16). Zazwyczaj jeden element char koduje jeden znak...

Przy pracy z ciągami tekstowymi należy pamiętać, że stringi w .NET są niezmienne (immutable)! Oznacza to po prostu, że raz utworzony łańcuch tekstowy nie może być modyfikowany (bez użycia refleksji lub kodu unsafe), a metody pozornie modyfikujące string, tak naprawdę zwracają nowy obiekt mający żądaną wartość.

Niezmienność stringów ma wiele zalet (o tym za chwilę), może jednak być źródłem problemów jeśli programista zapomni, że każda "zmiana" łańcucha tekstowego to tak naprawdę tworzenie nowej instancji klasy String. Chociaż CLR traktuje stringi w szczególny sposób, nadal są one typem referencyjnym, dla których to pamięć przydzielana jest na zarządzanej stercie.

Działanie tej pętli spowoduje utworzenie 10 tysięcy zmiennych typu string, z których wszystkie prócz ostatniej to śmieci, które będzie musiał zebrać Garbage Collector:

string s = string.Empty;

for (int i = 0; i < 10000; i++)
{
    s += "x";
}

Na poniższym obrazie widać fragment okna "Histogram by Size for Allocated Objects" aplikacji CLR Profiler. Widać jak kolejne obroty pętli powodowały alokacje pamięci na stercie dla coraz większych stringów:

By uniknąć tworzenia wielu niepotrzebnych obiektów należy użyć klasy StringBuilder, która umożliwia modyfikację tekstu bez każdorazowego tworzenia nowych obiektów:

StringBuilder sb = new StringBuilder();

for (int i = 0; i < 10000; i++)
{
    sb.Append("x");
}

string x = sb.ToString();

Ta prosta zmiana ma ogromny wpływ na ilość przydzielanej i zwalnianej pamięci. Spójrz na poniższe porównanie fragmentów okien "Summary" profilera:

 

Dlaczego projektanci .NET (tak samo jak np. twórcy Javy) zdecydowali się zaimplementować niezmienne łańcuch tekstowe?

Z przyczyn optymalizacyjnych (głównie ze względu na szybkość porównywania) łańcuchy tekstowe mogą być zapisywane w specjalnej tabeli (intern pool) utrzymywanej przez CLR. Chodzi o to by uniknąć tworzenia wielu zmiennych określających ten sam ciąg znaków. Poniżej przedstawiony jest kawałek kodu dowodzący tego, że zmienne posiadające ten sam łańcuch tekstowy mogą wskazywać na ten sam* obiekt:

string a = "xx";
string b = "xx";
string c = "x";
string d = String.Intern(c + c);

Console.WriteLine((object)a == (object)b); // True
Console.WriteLine((object)a == (object)d); // True

Gdyby łańcuchy tekstowe były modyfikowalne, zmieniając wartość zmiennej a, zmieniłoby się też wartość b oraz d.

Niezmienność stringów ma też pozytywne znaczenia w aplikacjach wielowątkowych - zmiana tekstu to utworzenie nowej zmiennej więc nie ma konieczności zakładania blokady (lock) w celu uniknięcia konfliktów związanych z jednoczesnym dostępem wielu wątków do jednego łańcucha tekstowego. Ma to tym większe znaczenie, że często dokonuje się autoryzacji pewnych operacji w zależności od tekstu (np. można blokować szczególne funkcjonalności usługi bazując na stringowym adresie klienta).

Ważnym powodem niezmienności jest powszechne użycie stringów jako kluczy w tablicach haszujących. Jaki sens miało by wyliczenie położenia elementu w tablicy, gdyby można było zmodyfikować wartość użytego klucza? Poniższy listing dowodzi, że "zmiana" wartości zmiennej użytej jako klucz nie ma wpływu na działanie tablicy:

string key = "abc";
Hashtable ht = new Hashtable();
ht.Add(key, 123);

key = "xbc";

Console.WriteLine(key); // xbc
Console.WriteLine(ht["abc"]); // 123

O tym co stałoby się w przypadku modyfikowalnych łańcuchów tekstowych można się przekonać za pomocą kodu używającego bloku unsafe w celu rzeczywistej zmiany wartości stringa użytego jako klucz:

unsafe
{
    string key = "abc";
    Hashtable ht = new Hashtable();
    ht.Add(key, 123);

    fixed (char* p = key)
    {
        p[0] = 'x';
    }

    Console.WriteLine(key); // xbc
    Console.WriteLine(ht["abc"]); // Nie znajdzie!
}

Niezmienność wiąże się też z tym, że string wewnętrznie przechowywany jest jako tablica, czyli struktura danych reprezentującą ciągłą przestrzeń adresową (dla której ze względów wydajnościowych nie dopuszcza się np. operacji wstawiania elementu).

* To czy literał łańcuchowy jest automatycznie dodawany do puli może być zależne od użycia narzędzia Ngen.exe lub ustawień CompilationRelaxations...

Niepewne działanie TextBox.MaxLength (użycie Fiddlera do modyfikacji żądania wysłanego przez IE)

by Miłosz Orzeł 9. January 2010 23:19

Ustawienie właściwości MaxLength w kontrolce asp:TextBox pozwala na ograniczenie ilości znaków jakie użytkownik może wprowadzić w pole tekstowe. Ustawienie to działa jednak jedynie gdy właściwość TextMode kontrolki ustawiona jest na SingleLine lub Password (nie zadziała w przypadku opcji MultiLine). Dzieje się tak ponieważ w dwóch pierwszych przypadkach TextBox renderowany jest jako element HTML input type="text", który może posiadać atrybut maxlength informujący przeglądarkę o konieczności ograniczenia ilości znaków. Gdy ustawi się tryb wielowierszowy, TextBox renderowany jest jako element textarea (który nie obsługuje atrybutu maxlenght).

Korzystając z właściwości MaxLenght musisz pamiętać o pewnym problemie! Otóż ograniczenie długości tekstu działa jedynie w przeglądarce. Po stronie serwera długość tekstu nie jest sprawdzana. Jeśli nie zastosujesz zatem odpowiedniej walidacji, złośliwy użytkownik będzie miał możliwość wpisania dowolnie długiego tekstu. Wystarczy tylko odpowiednio spreparować POST.

Do modyfikacji żądania wysyłanego na serwer posłużymy się darmowym narzędziem Fiddler. Jest to proxy służące do monitorowania komunikacji klient-serwer, posiadające również możliwość modyfikacji wysyłanych przez przeglądarkę żądań.

Do testów (sprawdzałem na .NET Framework 3.5, IE7 i Fiddler 2.2.4.2 beta) użyjemy prostej strony zawierającej TextBox i Button:

<asp:TextBox ID="TextBox1" runat="server" MaxLength="5"></asp:TextBox>
<asp:Button ID="Button1" runat="server" Text="Button" />

Jak widzisz maksymalna długość tekstu ograniczona jest (pozornie) do 5 znaków. Uruchom przygotowaną stronę i wypełnij pole tekstowe np. pięcioma literami "a".

Teraz uruchom Fiddlera (menu Narzędzia | Fiddler2) i ustaw automatyczne przerwanie (breakpoint) na requestach przeglądarki naciskając klawisz F11. W pasku stanu powinna pojawić się czerwona ikona z symbolem pauzy:

Po ustawieniu przerwań powróć na stronę testową i naciśnij przycisk by spowodować postback. POST towarzyszący naciśnięciu przycisku zostanie wyłapany przez Fiddlera (jeśli Fiddler nie przechwytuje ruchu przeczytaj uwagę na końcu tekstu). W oknie widocznym po lewej stronie kliknij w ostatni request (oznaczony czerwoną ikoną). Teraz możesz dowolnie zmodyfikować dane wysyłane na serwer.

W prawej części okna wybierz zakładkę "Inspectors". Następnie wybierz przycisk "WebForms" (przy pomocy tej opcji najłatwiej modyfikować pola formularzy). Czy widzisz w sekcji "Body" pole "TextBox1" z prowadzonym przez Ciebie pięcioliterowym tekstem? Dopisz do niego dowolne litery i naciśnij zielony przycisk "Run to Completion" by wznowić normalną komunikację przeglądarki z serwerem WWW.

Co stało się po przeładowaniu strony? Pole tekstowe posiada teraz tekst dłuższy od dopuszczalnego! Znaczy to, że infrastruktura ASP.NET nie sprawdza długości pola - po prostu aktualizuje właściwość Text:

* Istnieje prosty trik umożliwiający Fiddlerowi przechwytywanie ruchu na stronie z adresem localhost. Dodaj kropkę po słowie "localhost" i przeładuj stronę tak by jej adres wyglądał np. tak: http://localhost.:7913/Test

Debug oraz Release a wydajność kodu korzystającego z Microsoft AJAX Framework

by Miłosz Orzeł 26. December 2009 23:58

Istnieje kilka powodów, dla których nie powinno się umieszczać aplikacji ASP.NET w środowisku produkcyjnym z ustawieniem <compilation debug=”true”/> (czytaj…). Wbrew pozorom prędkość wykonywania instrukcji kodu po stronie serwera nie stanowi bardzo istotnego problemu. Inaczej jednak wygląda sytuacja po stronie klienta - gdy nasza aplikacja korzysta z javascriptowej biblioteki Microsoft AJAX Framework. Wówczas ustawienie Release/Debug* ma ogromny wpływ na szybkość wykonywania kodu aplikacji!

Doskonale widać to na przykładzie poniższej pętli, korzystającej z funkcji startsWith dostarczanej wraz z częścią kliencką pakietu ASP.NET AJAX.

for (var i = 0; i < 10000; i++) {
    'xxx'.startsWith('abc');
}

Pętla ta wykonuje się ok. 5 razy wolniej gdy używana jest wersja debug biblioteki JavaScript!  Czasy gdy JS był używany do wyświetlania alertów dawno minęły. Teraz gdy aplikacje webowe mogą posiadać tysiące linii kodu wykonywanego po stronie klienta, tak wielka różnica wydajnościowa może być sporym problemem (zwłaszcza gdy przeglądarka zgłosi komunikat o wolnym działaniu skryptu na stronie).

Skąd bierze się różnica w szybkości działania? Wynika ona z tego, że kod JavaScript opracowany przez Microsoft dla potrzeb tworzenia aplikacji ajaxowych, dostarczany jest w dwóch wersjach. Wersja, która jest wykorzystywana w trybie Debug, zawiera bloki kodu znacznie ułatwiające rozwijanie aplikacji – np. sprawdzają one ilość i typy parametrów podawanych w wywołaniu funkcji. Wersja dostarczana do przeglądarki w trybie Release nie zawiera tego rodzaju udogodnień – przez co skrypt wykonuje znacznie mniej pracy i działa szybciej.

Oto kod wykonywany przy wywołaniu metody startsWith w trybie Release (w celu ograniczenia wielkości plików pobieranych przez przeglądarkę kod nie zawiera komentarzy i zbędnych białych znaków):

String.prototype.startsWith=function(a){return this.substr(0,a.length)===a};

A to kod z trybu Debug:

String.prototype.startsWith = function String$startsWith(prefix) {
    /// <summary locid="M:J#String.startsWith" />
    /// <param name="prefix"></param>
    /// <returns type="Boolean"></returns>
    var e = Function._validateParams(arguments, [{
        name: "prefix",
        type: String
    }]);

    if (e) throw e;

    return (this.substr(0, prefix.length) === prefix);
}

Widzisz wywołanie funkcji _validateParams? Korzysta ona z kilku innych funkcji – łącznie kod walidujący parametry ma 155 linii (w wersji 3.5.30729.196 pliku MicrosoftAjax.debug.js).

Function._validateParams = function Function$_validateParams(params, expectedParams) {
    var e;
    e = Function._validateParameterCount(params, expectedParams);

    if (e) {
        e.popStackFrame();
        return e;
    }

    for (var i = 0; i < params.length; i++) {
        var expectedParam = expectedParams[Math.min(i, expectedParams.length - 1)];
        var paramName = expectedParam.name;

        if (expectedParam.parameterArray) {
            paramName += "[" + (i - expectedParams.length + 1) + "]";
        }

        e = Function._validateParameter(params[i], expectedParam, paramName);

        if (e) {
            e.popStackFrame();

            return e;
        }
    }

    return null;
* Domyślnie, to jaka wersja biblioteki JavaScript ASP.NET AJAX zostanie użyta, zależy od ustawienia atrybutu debug w elemencie configuration/system.web/compilation w pliku Web.config. Tryb emisji skryptów można jednak zmienić ustawiając atrybut ScriptMode kontrolki ScriptManager na Release lub Debug. Ustawienie to ma wyższy priorytet niż ustawienie w konfiguracji aplikacji.

Wysyłanie komunikatu do WebSphere MQ w UTF-8

by Miłosz Orzeł 18. December 2008 00:23

Jeśli chcesz wysłać do kolejki WebSphere MQ tekst zakodowany w standardzie UTF-8, pamiętaj o ustawieniu właściwości CharacterSet obiektu MQMessage na 1208. Jeśli tego nie zrobisz, tekst zostanie zakodowany z użyciem UTF-16 (CCSID 1200).

MQQueueManager queueManager = new MQQueueManager(...);
MQQueue queue = queueManager.AccessQueue(...);
MQPutMessageOptions putMessageOptions = new MQPutMessageOptions(...);
MQMessage message = new MQMessage();

message.Format = MQC.MQFMT_STRING;
message.CharacterSet = 1208;
message.WriteString("abcąćę");
queue.Put(message, putMessageOptions);

Stringi w .NET kodowane są przy użyciu UTF-16. Czasem jednak warto do wymiany informacji zastosować kodowanie UFT-8. Dlaczego? Wersja 8 jest oszczędniejsza, ponieważ znaki z tabeli US-ASCII są kodowane za pomocą 1 bajta, a nie za pomocą 2, jak w przypadku UTF-16. Jeśli wiec będziesz przesyłał tekst składający się jedynie z tego zestawu znaków, zużyjesz dwa razy mniej miejsca! W przypadku polskich liter znaki zostaną zakodowane na 2 bajtach (tak jak w UTF-16).

Oto porównanie bajtów dla tekstu "abcąćę":

UTF-8    61 62 63 C4 85 C4 87 C4 99
UTF-16   61 00 62 00 63 00 05 01 07 01 19 01

Badanie różnicy kolorów

by Miłosz Orzeł 8. December 2008 00:28

W .NET Framework do opisu koloru wykorzystywana jest struktura Color. Jej najważniejsze właściwości to: R, G oraz B. Odpowiadają one za intensywność barw podstawowych (czerwonej, zielonej i niebieskiej).* Właściwości te przyjmują wartości z zakresu od 0 do 255. Trzy bajty użyte do opisu składowych dają możliwość stworzenia ponad 16 milionów barw (2^24 = 16 777 216)!

RGB(0, 0, 0) to kolor czarny, (255, 255, 255) to kolor biały, (255, 0, 0) to czerwony a np. (255, 255, 0) to żółty... Struktura Color posiada również właściowść A (kanał alfa), która jest wykorzystywana w grafice komputerowej do określania stopnia przeźroczystości. Wartość 255 oznacza pełną nieprzeźroczystość.

Do zbadania różnicy między kolorami można posłużyć się odległością euklidesową (dystans między punktami). Wyobraź sobie trójwymiarowy układ współrzędnych, których osie oznaczymy odpowiednio przez R, G oraz B (składową A pomijamy). Gdy umieścisz w takim układzie dwa punkty odpowiadające kolorom i zmierzysz odległość między nimi, otrzymasz informację o stopniu podobieństwa kolorów. Dzięki temu będziesz mógł np. wykryć na obrazie wszystkie miejsca mające odcień zieleni...

Color c1 =  Color.White;
Color c2 = Color.Black;

int odlegloscR = (c1.R - c2.R) * (c1.R - c2.R);
int odlegloscG = (c1.G - c2.G) * (c1.G - c2.G);
int odlegloscB = (c1.B - c2.B) * (c1.B - c2.B);

double roznicaKoloru = Math.Sqrt(odlegloscR + odlegloscG
 + odlegloscB);

W powyższym kodzie celowo nie korzystałem z funkcji Math.Pow. Jej zastosowanie przy takiej operacji jak podnoszenie do potęgi całkowitej to marnotrastwo CPU (gdy trzeba przebadać wiele punktów ma to znaczenie). Jeśli wyliczenie zmiennej roznicaKoloru ma słyżyć jedynie porównywaniu barw można by również zrezygnować z pierwiastkowania...

* Model RGB opisuje zjawisko syntezy addytywnej czyli mieszanie się swiatła emitowanego (np. przez monitor).

Po co?

Nie wyobrażam sobie pracy programisty bez setek stron, na których ludzie „mar- nując” swój wolny czas dzielą się tym czego udało im się dowiedzieć. Spróbuję zatem sam (w miarę swoich możliwości) dodać nieco pożytecznych informacji do zasobów Sieci... - o mnie

Language

Click here to see English version.