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.

10/12/2008

Czemu zdarzenia nie działają???

Home

Ostatnio pomogłem rozwiązać dwa problemy z "nie działającymi" zdarzeniami. Jak to najczęściej bywa, znając rozwiązanie, problem wydaje się banalnie prosty. Ponieważ jednak dojście do rozwiązania nie zawsze jest już tak proste postanowiłem napisać ten post.

Ogólnie problem został mi przedstawiony mniej więcej w taki sposób (luźny cytat): Podczepiłem się pod zdarzenia kilku kontrolek ale po wykonaniu post back'a do strony, metody obsługi zdarzeń nie są wołane.

W obu wspomnianych sytuacjach obserwowany efekt był taki sam (metody obsługi zdarzeń nie były wołane), natomiast przyczyna błędu troszeczkę inna. Błędu, ponieważ to oczywiście nie bug w maszynerii ASP.Net tylko najzwyklejszy w świecie błąd programisty.

Problem numer 1

W pierwszym przypadku programista dynamicznie tworzył kontrolkę, subskrybował jej zdarzenia, a następnie dodawał ją do strony. Dla ustalenia uwagi przyjmijmy, że była to kontrolka ListBox. Opisywany kod mógł więc wyglądać następująco:
...
ListBox lb = new ListBox();
lb.AutoPostBack = true;
lb.SelectedIndexChanged += new EventHandler(lb_SelectedIndexChanged);
lb.Items.Add("a");
lb.Items.Add("b");
lb.Items.Add("c");

this.Panel.Controls.Add(lb);
...
Sam w sobie, kod ten jest jak najbardziej poprawny. Błąd polegał na tym, że kod ten był wykonywany tylko w przypadku inicjalnego odwołania do strony. W przypadku post back'a czyli kiedy właściwość IsPostBack była równa true już nie. A skoro kontrolka nie została utworzona to zdarzenie nie mogło zostać wygenerowane.

Problem numer 2

W drugim przypadku programista używał statycznie osadzonej na stronie kontrolki DataList, zasilał ją danymi i wołał metodę DataBind. Kod strony przypominał koncepcyjnie kod poniżej:
...
this.DataList.DataSource = new string[] { "a", "b", "c" };
this.DataList.DataBind();
...
...
<asp:DataList ID="DataList" runat="server" EnableViewState="false">
   <ItemTemplate>
      <asp:Button ID="Button" runat="server" Text="<%# Container.DataItem %>" OnClick="OnClick" />
   </ItemTemplate>
</asp:DataList>
...
Podobnie jak wcześniej, strona nie działała tak jak powinna, ponieważ w przypadku post back'a nie była wołana metoda DataBind kontrolki DataList i przyciski nie były tworzone i w związku z tym zdarzenia nie mogły zostać wygenerowane. W tym jednak przypadku, pośrednią przyczyną błędu był fakt, że View State został wyłączony. Gdyby był włączony, przyciski zostałyby odtworzone na jego podstawie.

Wnioski

Konkluzja jest bardzo prosta. 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.

08/12/2008

Włączanie i wyłączanie kaskadowych arkuszy stylów

Home

Jeśli potrzebujemy szybki i łatwo zaimplementować włączanie i wyłączanie kaskadowych arkuszy stylów na stronie możemy zastosować taki kod:
...
<head runat="server">
    <link id="link" 
        type="text/css" 
        rel="Stylesheet"
        href="~/style.css" 
        runat="server" />
</head>
...
Teraz wystarczy sterować widocznością serwerowej kontrolki Html. Jeśli kontrolka nie będzie widoczna:
this.link.Visible = false;
to nie zostanie wzięta pod uwagę podczas renderowania strony i styl nie zostanie zastosowany do strony. Odwrotnie, jeśli kontrolka będzie widoczna:
this.link.Visible = true;
to podczas renderowania strony zostanie uwzględniona i styl zostanie zastosowany do strony. Bardzo proste ale skuteczne.

04/12/2008

Transformacje Xsl i przestrzenie nazw XML

Home

Przy używaniu transformacji Xsl należy pamiętać o przestrzeniach nazw Xml. Załóżmy, że mamy dokument Xml i transformację Xsl do jej przetwarzania:
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type='text/xsl' href='Transformation.xsl'?>
<A>
   <B>
      bbb
   </B>
   <B>
      bbb
   </B>
</A>
Transformajca wygląda natomiast tak:
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet version="1.0"  xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" indent="yes" standalone="no" omit-xml-declaration="yes" encoding="windows-1250" />

<xsl:template match="B" >
   <LI>
      <xsl:value-of select="current()"/>
   </LI>
</xsl:template>

<xsl:template match="A" >
   <HTML>
      <HEAD>
      </HEAD>
      <BODY>
         <UL>
            <xsl:apply-templates select="B"/>
         </UL>
      </BODY>
   </HTML>
</xsl:template>

</xsl:stylesheet>
Wynikiem działania przedstawionej transformacji na przykładowym dokumencie Xml powinna być lista:
  • bbb
  • bbb
Wynik będzie zupełnie inny jeśli zmodyfikujemy dokument Xml w następujący sposób:
...
<A xmlns="a.b.c">
...
Po tej zmianie otrzymamy taki, mało przyjazny rezultat transformacji:
bbb bbb
Aby rozwiązać problem należy zmodyfikować definicję transformacji poprzez jawne wskazanie przestrzeni nazw z jakiej pochodzą przetwarzane węzły dokumentu Xml. Po pierwsze należy podać definicję nowej przestrzeni nazw poprzez dodanie do węzla xsl:stylesheet atybutu xmlns:test="a.b.c". Oczywiście można podać inną skróconą nazwę przestrzeni nazw niż test. Następnie należy dodać przedrostek test: przed każdym odwołaniem do węzła z dokumentu np.:
...
<xsl:stylesheet version="1.0"  xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:test="a.b.c">
...
...
<xsl:template match="test:B" >
   <LI>
      <xsl:value-of select="current()"/>
   </LI>
</xsl:template>
...

01/12/2008

LoadControl

Home

Czy poniższy kod zawierający wywołanie metody LoadControl wydaje się wam poprawny? Jeśli tak to zapraszam do dalszej lektury.

protected void Page_Load(object sender, EventArgs e)
{
    ...
    Control control = LoadControl("~/MySimpleUserControl.ascx");
    PlaceHolder1.Controls.Add(control);

    ((MySimpleUserControl)control).BackColor = Color.Yellow;
    ...
}
Niestety ale kod ten będzie działał poprawnie tylko do momentu kiedy dla kontrolki zostanie włączony mechanizm Output Cache, na przykład w następujący sposób:

<%@ OutputCache Duration="60" VaryByParam="None" %>
W takim przypadku, przy następnym ładowaniu strony, na której umieszczono kontrolkę, pojawi się wyjątek InvalidCastException z komunikatem: "Unable to cast object of type ‘System.Web.UI.PartialCachingControl’ to type ‘MySimpleUserControl’.". Dzieje się tak ponieważ metoda LoadControl, w przypadku kiedy włączone jest cache'owanie dla kontrolki, zwraca obiekt klasy PartialCachingControl, a nie klasy MySimpleUserControl jak mogłoby się wydawać. W takie sytuacji do kontrolki możemy się dostać przez właściwość PartialCachingControl.CachedControl. Poprawny kod powinien, więc wyglądać tak:

protected void Page_Load(object sender, EventArgs e)
{
    ...
    Control control = LoadControl("~/MySimpleUserControl.ascx");
    PlaceHolder1.Controls.Add(control);

    MySimpleUserControl c = control as MySimpleUserControl;
    if(c == null)
    {
        PartialCachingControl pc = control as PartialCachingControl;
        c = pc.CachedControl as MySimpleUserControl;
    }

    if(c != null)
       c.BackColor = Color.Yellow;
    ...
}
Dodatkowe sprawdzenie if(c != null) jest potrzebne ponieważ właściwość PartialCachingControl.CachedControl zwróci null jeśli kontrolka znajduje się już w cache'u. Innymi słowy wartość różna od null zostanie zwrócona tylko wtedy kiedy kontrolka nie została jeszcze umieszczona w cache albo zawartość cache przestała być ważna.

Należy również pamiętać aby odwołanie do PartialCachingControl.CachedControl było zawsze poprzedzone umieszczeniem kontrolki na stronie np.: PlaceHolder1.Controls.Add(control);. W przeciwnym wypadku właściwość PartialCachingControl.CachedControl zawsze zwróci null.

W przypadku kiedy korzystamy z cache'owania i mamy na stronie statycznie osadzone kontrolki również należy zachować ostrożność. Jeśli kontrolka zostanie pobrana z cache to nie zostanie zainicjowana i nie możemy w związku z tym odwołać się do niej w kodzie naszej strony. Oczywiście jest to jak najbardziej prawidołe zachowanie - na tym polega idea mechanizmu Output Cache.

28/11/2008

Modelowanie procesów biznesowych

Home

Ostatnio zainteresowałem się modelowaniem procesów biznesowych. Spodziewałem się istnienie kilku standardów ale rzeczywistość naprawdę mnie zaskoczyła. Świat BPM okazał się naprawdę bogaty. Sądzę, że porównanie do dżungli będzie nawet bardziej odpowiednie. Mamy więc: BPDM, BPEL4WS, BPML, BPMN, UML Activity Diagram, WSFL, XLANG, XDPL i sporo więcej. W poście tym chciałbym troszeczkę usystematyzować ten bałagan i wyjaśnić jak jak to rozumiem.

Wstęp

Zacznijmy od tego, że w procesie BPM należy wyróżnić dwa zasadnicze poziomy:
  • Poziom modelowania – poziom, w którym działają analitycy, konsultanci itd.
  • Poziom wykonania – poziom, w którym pracują inżynierzy oprogramowania itd.
Na styku tych dwóch (ale moim zdaniem bardziej na poziomie modelowania) poziomów znajduje się analityk procesów biznesowych.

Poziom modelowania

Użytkownicy pracujący na tym poziomie posługują się wysoko poziomowymi narzędziami i notacjami do zamodelowania procesów biznesowych. Odpowiednich narzędzi jest bardzo dużo ale niewątpliwie najpopularniejszą i najbardziej rozpowszechnioną notacją jest BPMN – Business Process Modeling Notation, która została zaproponowana przez BPMI – Business Process Modeling Initiative (od 2005 połączone z OMG).

BPMN to nie tylko notacja. Definiuje nie tylko elementy diagramu ale również ich semantykę. Specyfikacja BPMN nie określa natomiast sposobu w jaki stworzony diagram ma zostać zapisany (zserializowany). Do tego celu bardzo często używa się innej specyfikacji zaproponowanej przez IBM i Microsoft: XDPL – XML Process Definition Language.

W tym miejscu należy wspomnieć o najnowszym członku rodziny BPM czyli o specyfikacji zaproponowanej przez OMG: BPDM – Business Process Definition Metamodel. Finalna wersja tej specyfikacji jest bardzo świeża - pochodzi z czerwca tego roku. BPDM stanowi w pewnym sensie połączenie wymienionych wcześniej standardów: XDPL oraz BPMN ale nie tylko. Przyszłość tego rozwiązania nie jest jeszcze określona i należy poczekać czy zdobędzie popularność. Z pewnością pokłada się w niej duże nadzieje.

Istnieje również kilka innych rozwiązań ale odnoszę wrażenie, że są mało popularne dlatego nie przytacza, ich opisu w tym poście.

Poziom wykonania

Po zdefiniowaniu procesu chcielibyśmy zapewne zasilić nim jakiś silnik, ktory go wykona. W tym celu należałoby przetłumaczyć diagram do formatu/opisu zrozumiałego przez określoną technologię. W obecnej chwili najpopularniejsze wydają się dwie specyfikacje (oparte o XML): BPEL – Business Process Execution Language oraz BPML4WS – Business Process Modeling Language For Web Services. Pierwsza została zaproponowana przez BPMI, a druga przez IBM, BEA Systems oraz Microsoft. Należy zauważyć, że BPML4WS to następca BPML, który nie jest już wspierany. BPEL został oparty o WSFL oraz XLANG.


Rysunek pochodzi z: The BPMN-XPDL-BPEL value chain

Do poczytania

Zarządzanie procesami biznesowymi – standardy notacji i nie tylko

http://modelowanie.wordpress.com/category/bpmnbpml/

Business Process Modeling and Standarization

XPDL,BEPL,JPDL,BPMNS,BPDM et al.. Standards and More Standards

The BPMN-XPDL-BPEL value chain

BPMN, BPEL, BPML and XPDL, an attempt to make some order in the business modeling jungle

A Comparison of XPDL, BPML and BPEL4WS Cape Visions

Business Process Model and Notation (BPMN) 2.0 Request For Proposal


27/11/2008

throw; vs. throw ex;

Home

Nie każdy zdaje sobie z tego sprawę ale poniższe dwie konstrukcje mają inną semantykę:

         try
         {
            ...
         }
         catch(Exception ex)
         {
            throw;
         }
         
         try
         {
            ...
         }
         catch(Exception ex)
         {
            throw ex;
         }
         
Różnica jest taka, że stosując pierwszą z nich nie tracimy informacji zawartej w stosie wywołań (stack trace). To znaczy, że jeśli ponownie rzucony przez nas wyjątek zostanie złapany w kolejnym bloku try/cacth to będzie tam dostępna pełna informacja o stosie wywołań począwszy od pierwotnego źródła wyjątku. W drugim przypadku stos wywołań będzie zawierał ograniczoną informację - będzie wyglądał tak, jakby pierwotnym źródłem wyjątku była metoda, w której został on złapany i ponownie rzucony!

24/11/2008

Zakamarki Visual Studio 2005/2008 (cz. 2)

Home

Zapraszam do zapoznania się z kolejną porcją ciekawych i mało znanych funkcji Visual Studio.

Breakpoint w pętli

Bardzo przydaną funkcją jest możliwość postawienia breakpoint'a w definicji pętli for lub foreach. Załóżmy, że mamy taki kod:
for(int i = GetValue(); i < GetLimit(); i++)
{
  ...
}
Domyślne zachowanie środowiska jest takie, że po kliknięciu linii, w której znajduje się początek pętli i naciśnięciu przycisku F9 breakpoint zostanie ustawiony na części inicjalizacyjnej pętli czyli uzyskamy taki efekt:
for(int i = GetValue(); i < GetLimit(); i++)
{
  ...
}
Czasami, a nawet częściej niż czasami chcielibyśmy aby debugger zatrzymał się w części sprawdzającej warunek pętli. Nic prostszego. Wystarczy przesunąć kursor i ponownie nacisnąć F9. Uzyskamy efekt jak poniżej:
for(int i = GetValue(); i < GetLimit(); i++)
{
  ...
}
Oczywiście debugger możemy zatrzymać również w instrukcji interacji. Podobnie możemy postąpić z pętlą foreach.

Testowanie przy pomocy okna Immediate

Ciekawym sposobem na szybkie testowanie metod statycznych jest użycie okna Immediate. Jeśli nie jest ono standardowo widoczne to znajdziemy je w Debug -> Windows. Po pierwsze okno to pozwala w czasie debugowania wywoływać metody obiektów, zmieniać ich właściwości itd. Po drugie, co jest nawet ciekawsze, umożliwia wywołanie metody statycznej kiedy środowisko nie znajduje się w trybie debugowania. Wykonanie takiej operacji spowoduje uruchomienie debugera i o ile, w metodzie statycznej znajduje się breakpoint, jego zatrzymanie. Dzięki temu nie musimy tracić czasu na pisanie krótkich programików tylko po to aby przetestować daną metodę statyczną. Po trzecie co jeszcze ciekawsze okno Immediate pozwala również w podobny sposób testować zwykłe metody klas. Załóżmy, że napisaliśmy klasę Test i zdefiniowaliśmy w niej metodę Fun(). W oknie Immediate może wpisać:
Test t = new Test(); 
t.Fun(); 
Podobnie jak wcześniej. Jeśli w metodzie znajdował się breakpoint debugger zatrzyma się na nim. Jesli nie, metoda zakończy swoje działanie. Oczywiście technika ta pozwala testować tylko stosunkowe proste scenariusze ale tak czy inaczej ułatwia i przyspiesza tworzenie dobrego kodu.

Opisane techniki testowałem w środowiskach Visual Studio 2005 oraz Visual Studio 2008.

22/11/2008

ADO.NET Data Services, Jak to ugryźć?

Home

Wszystkich zainteresowanych rozpoczęciem zabawy z technologią Microsoft'u ADO.NET Data Services (nazwa kodowa Astoria) chciałbym zachęcić do zapoznania się z serią filmików spod znaku How Do I:
Astoria umożliwia naprawdę łatwy i szybki sposób udostępniania danych z różnych źródeł w sieci (w szczególności może to być oczywiście baza relacyjna). Dostęp do danych został zorganizowany zgodnie z podejściem typu REST czyli upraszczając aby uzyskać dostęp do zasobu musimy znać jego URI. Każdy z filmików pokazuje krok po kroku jak coś zrobić przy pomocy ADO.NET Data Services. Obejrzenie każdego z nich zajmie wam kilkanaście minut (oczywiście trzeba znać język angielski). Bardziej dogłębne (i znacznie dłuższe) wprowadzenie znajdziemy tutaj Developing Applications Using Data Services. Aby rozpocząć używanie Astorii potrzebujemy Visual Studio 2008 z Service Pack 1. Wersję Express już z Service Pack 1 znajdziemy tutaj.

20/11/2008

Tajemnica yield

Home

Czy zastanawialiście się kiedyś jak działa słowo kluczowe yield? Jeśli ktoś nie kojarzy tej konstrukcji to w telegraficznym skrócie pozwala ona (między innymi) w bardzo łatwy sposób zaimplementować interfejs IEnumerable. Interfejs ten wymagana dostarczenia tylko jednej metody, która powinna zwrócić instancję klasy implementującej IEnumerator. Zaimplementowanie tego interfejsu nie powinno przysporzyć znacznych trudności ale wymaga już trochę większego nakładu pracy. Przykładowe, uproszczone użycie yield mogłoby wyglądać tak:
public class Counter : IEnumerable
{
  private int i = 0;

  public Counter(int i)  
  {
    this.i = i;
  }

  public IEnumerator GetEnumerator()
  {
    while(i>0)
      yield return i--;
  }
}
Tylko tyle i aż tyle. Nie musimy pisać kodu dla metod MoveNext, Reset czy też właściwości Current wymaganych przez IEnumerator. Zamiast tego otrzymujemy kilkulinijkowy elegancki kod. Możemy oczywiście napisać teraz coś w tym rodzaju:
foreach (int i in new Counter(10))
  Console.WriteLine(i);
Zanim zaczniecie czytać dalej zastanówcie się teraz przez chwilę w jaki sposób to działa. Teraz możemy przejść do clue tego posta. Otóż okazuje się, że słowo kluczowe yield to nic innego jak lukier syntaktyczny. Nie kryje się zanim żadna magia. Po prostu kompilator po napotkaniu yield generuje dynamicznie kod enumeratora. Możemy to bardzo łatwo zobaczyć korzystając z reflektora Lutz Roeder’s Reflector. To co zobaczymy będzie koncepcyjnie podobne do kodu poniżej.
public class Counter
{
...
  public IEnumerator GetEnumerator()
  {
    //Utworzenie enumeratora
    InnerEnumerator ie = new InnerEnumerator(0);
    //Ustawienie wskazania na obiekt, po którym będziemy enumerować
    ie.current = this;

    reutrn ie;
  }

  private sealed class InnerEnumerator : IEnumerator
  {
    //Stan w jakim znajduje się enumerator
    //0 - stan początkowy
    //1 - stan pośredni
    //-1 - stan końcowy
    private int state;
    //Ostatnia wartość zwrócona przez enumerator
    private int current;
    //Obiekt, po którym będziemy enumerować
    public Counter counter;

    public InnerEnumerator(int state)
    {
      //Ustawienie stanu inicjalnego
      this.state = state;
    }

    public bool MoveNext()
    {
      switch (this.state)
      {
        case 0:
        //Jeśli warunek początkowy rozpoczęcia działania enumeratora 
        //nie będzie spełniony to przechodzimy do stanu końcowego
        this.state = -1;
        //Jeśli są jeszcze jakieś wartości do odwiedzenia przez enumerator
        while (this.counter.i > 0)
        {
          //Wyznacz kolejną wartość
          this.current = this.counter.i--;
          //Być może są jeszcze jakieś wartości do odwiedzenia
          //dlatego ustawiamy stan pośredni
          this.state = 1;
          return true;
          LABEL:
          //Jeśli nie będzie już wartości do odwiedzenia to 
          //należy zakończyć pracę enumeratora
          this.state = -1;
        }
        break;

        case 1:
          //Kontynuujemy pracę enumeratora
          goto LABEL;
      }

      //Enumerator odwiedził wszystkie elementy
      return false;
    }

    public object Current
    {
      get{ return this.current; }
    }
    ...  
  }
...
}
Z kodu usunąłem niepotrzebne w tym kontekście fragmenty i zmieniłem go, żeby był prostszy w zrozumieniu. Wygenerowany kod jest prawidłowy jako MSIL ale jako C# nie skomiluje się ze względu niedozwolone użycie instrukcji goto. Całość jest chyba łatwa do zrozumienia, a najistotniejsza jest metoda MoveNext(), w której tak naprawdę możemy zobaczyć to co napisaliśmy w GetEnumerator. Każda iteracja pętli powoduje przejście do następnego elementu. Polecenia skoku użyto aby przy kolejnych wywołaniach MoveNext wskoczyć do środka pętli i kontynuować jej wykonanie. Proste, nie :)

Pożytki płynące z używania Process Monitor'a

Home

W poście tym chciałbym zachęcić do używania darmowego narzędzia Process Monitor, które umożliwia monitorowanie wszelkiej aktywności w systemie operacyjnym dotyczącej zasobów takich jak: pliki, klucze rejestru, połączenia sieciowe itd.

Pożytki płynące z tego narzędzia opiszę na swoim przypadku. Ostatnio napisałem prostą bibliotekę, która parsuje plik Xml, przetwarza jego zawartość przy pomocy transformacji Xsl w celu stworzenia plików Html i finalnie uruchamia kompilator pomocy w celu wytworzenia pliku chm. Biblioteka nie jest skomplikowana i bardzo szybko udało się uzyskać pożądany efekt (prawie). Niestety ale okazało się, że kompilator pomocy nie potrafi przetworzyć wszystkich plików Html i generuje dla nich błąd HHC5003: Error: Compilation failed while compiling... Co ciekawe efekt ten był obserwowany tylko w przypadku programowego uruchomienia kompilatora przy pomocy klasy Process. Przy ręcznym uruchamiania wszystko działało prawidłowo. Po bliższym przyjrzeniu okazało się, że kompilator zgłaszał błędy zawsze dla tych samych plików. W przypadku kiedy lista plików została powiększona błąd zaczął być zgłaszany dla nowych plików! Pliki, które wcześniej zdawały się nieprawidłowe okazywały się nagle jak najbardziej w porządku.

Nie udało mi się znaleźć dokładnych informacji na temat zgłaszanego przez kompilator błędu z wyjątkiem tego, że przyczyną może być brak dostępu do pliku. Postanowiłem więc przyjrzeć się procesowi kompilacji przy pomocy Process Monitor'a. Ponieważ aplikacja w trybie domyślnym wyświetla ogromną ilość danych (setki tysiące wierszy) postanowiłem skupić się tylko na wpisach dotyczących jednego z plików, dla których kompilator zgłaszał błąd. Po włączeniu filtrowania uzyskałem taki wynik:



Na niebiesko oznaczyłem wiersz, w którym widać, że kompilator nie uzyskał dostępu do pliku z powodu błędu SHARING VIOLATION. Błąd ten oznacza, że plik był w tym momencie w użyciu. Ponieważ problematyczny plik Html był generowany w całości przez mój kod mogłem założyć, że błąd tkwi po mojej stronie. Jak się okazało popełniłem akademicki błąd i nie zamykałem strumienia do pliku (do tej pory biję się w pierś). Oczywiście konstrukcja using rozwiązała problem.

Cały czas pozostaje jednak pytanie czemu błąd pojawiał się tylko dla określonych plików. Strumień nie był przecież zamykany dla każdego dynamicznie wygenerowanego pliku Html. W tym przypadku odpowiedź też nie jest skomplikowana - musiał zadziałać mechanizm automatycznego zwalniania pamięci garbage collector.

19/11/2008

Zakamarki Visual Studio (cz. 1)

Home

Visual Studio to potężne narzędzie, o ogromnych możliwościach, które pozwala tworzyć i debugować programy w łatwy i przyjemy sposób. Dobra znajomość swojego środowiska pracy do podstawa dla każdego programisty. Dlatego poniżej zamieszczam opis kilku "zaawansowanych" narzędzi udostępnionych w Visual Studio. Tak naprawdę prezentowane przeze mnie techniki nie są ani trudne, ani skomplikowane w użyciu. Nie ulega jednak wątpliwości, że są stosunkowo mało znane. Działają poprawnie w przypadku języka C# (dla innych języków może być inaczej).

Okienko Find

Z pewnością każdy kiedyś korzystał z tego narzędzia aby szybko wyszukać w kodzie interesującą go frazę. Ale możliwości okienka Find są znacznie większe. Po pierwsze wpisanie do niego nazwy jakieś metody i naciśnięcie przycisku F9 spowoduje wstawienie breakpoint'a do każdej metody o podanej nazwie. Moim zdaniem bardzo przydatna funkcjonalność jeśli mamy w projekcie jedną lub więcej, wielokrotnie przeciążonych metod. Do okienka Find możemy również wpisać nazwę pliku (razem z rozszerzeniem). Teraz jeśli naciśniemy kombinację Ctrl+Shift+G i plik o podanej nazwie istnieje to zostanie otworzony gotowy do edycji. Przydaje się przy dużym drzewie projektów z rozbudowaną hierarchią plików i folderów.

Podglądanie cudzych metod

Przypuśćmy przez chwilę, że pracujemy ze złośliwym programistą, który nie chce udostępnić innym swoich kodów. Przypuśćmy dalej, że w kodzie złośliwego programisty jest generowany wyjątek. Wyjątek ten powstaje głęboko w kodzie złośliwego programisty i oznacza, że do metody został przekazany ciąg znaków o błędnym formacie. Wspomniany ciąg znaków dostarczany jest przez nas i może to być np.: connection string.

Co więcej programista jest tak uparty, że twierdzi, że to nie jego wina oraz, że z pewnością przekazujemy do jego metody złe dane. Co gorsza jest na tyle sprytny, że zabezpieczył swoje biblioteki przed programem takim jak Lutz Roeder’s Reflector. Tak na marginesie to genialne narzędzie, niezbędne w pracy każdego programisty .Net. Należy jeszcze dodać, że ten programista to syn szefa :)

My wiemy swoje i on wie swoje. Nie mamy dostępu to kodu biblioteki złośliwego programisty ale oczywiście znamy stack trace. Znamy więc nazwy kolejno wołanych metod aż do wystąpienia wyjątku. Jedyna nasza szansa to udowodnić złośliwemu programiście, że nasze dane są prawidłowe i to w jego kodzie jest błąd.

I tutaj wkracza do akcji Visual Studio. Okazuje się, że można postawić breakpoint w metodzie, dla której nie mamy kodu źródłowego. Kiedy debugger zatrzyma się będziemy mogli w oknie Locals zobaczyć wartości wszystkich argumentów przekazanych do metody! W celu postawienia takiego breakpoint'u korzystamy z bardzo prostego sposobu: otwieramy okno New Breakpoint, w polu Function wpisujemy nazwę metody, naciskamy przycisk Ok i gotowe. Do okna New Breakpoint dostajemy się z poziomu standardowego okna Breakpoints (Debug -> Windows -> Breakpoints) wybierając przycisk New, a następnie polecenie Brak at Function...

Okno Call Stack

Run To Cursor to bardzo znane polecenie, które umożliwia rozpoczęcie debugowania i wskazanie miejsca w kodzie, w którym debugger ma się zatrzymać (pod warunkiem, że nie zatrzyma się wcześniej z powodu breakpoint'a). Niewiele osób jednak wie, że polecenie to jest dostępna z poziomu okna Call Stack. Pozwala wskazać, do którego miejsca chcemy zwinąć stos. Wystarczy wybrać interesującą nas metodę i z menu kontekstowego wybrać tę komendę. W bardzo podobny sposób można przy pomocy okna Call Stack postawić breakpoint. W tym celu podobnie jak wyżej wybieramy interesującą nas metodę, zaznaczamy ją i naciskamy przycisk F9 (albo wywołujemy menu kontekstowe i wybieramy Breakpoint -> Insert Breakpoint).

Podsumowanie

Mam nadzieję, że opisane przeze mnie narzędzia i techniki okażą się przydatne. W następnej części opiszę kolejną porcję ciekawych funkcji Visual Studio.

Opisane techniki testowałem w środowiskach Visual Studio 2005 oraz Visual Studio 2008.