04/06/2009

Bardzo użyteczne narzędzie do pracy z WMI

Home

Przeglądając dzisiaj fora internetowa, dotyczące platformy .NET, w odpowiedzi na jedno z zadanych pytań znalazłem wzmiankę o bardzo użytecznym narzędziu WMI Code Creator v1.0. Narzędzie to pozwala na wygenerowanie kodu używającego WMI (ang. Windows Management Instrumentation) do wykonywania różnego rodzaju zadań zarządzania: odczytywanie danych, oczekiwanie na zdarzenia WMI czy wywoływanie metod z klas WMI.

WMI nie jest za pewne narzędziem używanym w codziennej pracy ale mogącym się czasem przydać. Przy pomocy tej technologii możemy dobrać się do szczegółowych inforamacji na temat BIOS'u, procesora, dysków i innych urządzeń, procesów, usług oraz najróżniejszych ustawień systemu operacyjnego. WMI posługuje się dobrze znanymi pojęciami takimi jak: przestrzenie nazw, klasy, metody, zdarzenia i właściwości. Przestrzenie nazw zawierają klasy dotyczące poszczególnych obszarów zarządzania. Klasy modelują różne byty w tych obszarach. Właściwości tych klas to różne parametry konfiguracyjne. Na przykład dla klasy modelującej procesor możemy odczytać jego rodzinę, producenta czy liczbę rdzeni. Dla klasy modelującej proces została zdefiniowana metoda pozwalająca go zamknąć. Przykładem zdarzenia na jakie można oczekiwać jest natomiast zmiana statusu usługi.

Z poziomu platformy .NET do komunikacji z WMI służą klasy z przestrzeni nazw System.Management. Ich użycie nie jest bardzo skomplikowane, główna trudność polega na mnogości przestrzeni nazw i klas WMI. Nie sposób tego spamiętać. Tutaj z pomocą przychodzi wspomniane narzędzie. Przy jego pomocy możemy wybrać interesującą nas przestrzeń, potem klasę i jej właściwości, a następnie wygenerować kod w jednym z trzech języków: C#, Visual Basic .NET, Visual Basic Script, który odczyta parametry jakie wybraliśmy, wywoła metodę lub będzie oczekiwał na zdarzenia WMI. Co bardzo przydatne WMI Code Creator v1.0 pozwala podejrzeć wartości wybranych właściwości.

02/06/2009

Problemy z instalatorem

Home

A problem has been encountered while loading the setup components. Canceling setup

Komunikat ten, objaśniający bardzo dokładnie przyczynę błędu ;), napotkałem próbując doinstalować do Visual Studio 2008 Team System komponenty związane z obsługą języka C++. Rozwiązanie problemu okazało się trywialne ale dojście do niego zajęło mi trochę czasu.

Rozwiązywanie problemu rozpocząłem od sprawdzenia czy w katalogu z plikami instalacyjnymi znajdują się inne pliki z setup w nazwie lub podobne i czy można je uruchomić. Kiedy to nie pomogło skopiowałem cały katalog z instalatorem i potrzebnymi plikami na dysk lokalny przyjmując, że problem może wynikać z tego, że uruchamiam go z dysku zdalnego. Niestety ale to również nie pomogło.

Zacząłem szukać pomocy u pana Google. Natrafiłem na forum, na którym radzono aby po prostu odinstalować środowisko i wszystkie jego komponenty, a następnie zainstalować je jeszcze raz. Pewnie by podziałało ale nie chciałem poświęcać cennego czasu na ponowne instalowanie wszystkiego czego potrzebuję.

Po dalszych poszukiwaniach znalazłem wskazówkę mówiącą abym poszukał w katalogach tymczasowych logów instalatora i zobaczył co się w nich znajduje. Rada była o tyle mało użyteczna, że nie zawierała informacji gdzie dokładnie szukać plików i jak się nazywają. Uruchomiłem więc Process Monitor i zacząłem przeglądać jakie pliki otwiera instalator VS. W moim przypadku nazywały się: dd_install_vs_vstdcore_90.txt oraz dd_error_vs_vstdcore_90.txt i znajdowały się w katalogu:
C:\Documents and Settings\username\Local Settings\Temp
W jednym z plików znalazłem wpisy zawierające tekst ERRORLOG EVENT, coś w tym rodzaju:
[06/02/09,12:25:45] setup.exe: ***ERRORLOG EVENT*** : ISetupModule::SetManager() failed in ISetupManager::LoadSetupObjectGuid() : vs_setup.dll
Nie zastanawiając się długo wrzuciłem treść loga do Googla i jak to zwykle otrzymałem dużoooooooo wyników. Jedna z pierwszych stron, zresztą forum MSDN, dotyczyła VS 2005 ale wyglądała obiecująco. Wyczytałem na niej, że problem ustępuje po wyłączeniu antywirusa, a dokładniej programu firmy Kaspersky. Korzystam co prawdy z produktu konkurencji ale stwierdziłem, że nie zaszkodzi spróbować. Niestety ale i to rozwiązanie okazało się chybione. Kiedy już prawie straciłem nadzieję kilka pozycji niżej na liście wyników znalazłem ten post. Żeby nie przedłużać rozwiązanie problemu to:

W przypadku Visual Studio 2008 z zainstalowanym service pack'iem pierwszym (nie wiem jak z kolejnymi) instalator należy uruchamiać z poziomu okna Add or Remove Programs w panelu sterowania.

Data Binding i dobre praktyki programistyczne

Home

The data source for GridView with id 'GridView1' did not have any properties or attributes from which to generate columns. Ensure that your data source has content.

Czy spotkaliście się z takim błędem pomimo, że byliście pewni, że poprawnie zasilacie kontrolkę lub powiązane z nią źródło danych? Jeśli tak to problem był związany z użytymi strukturami danych. Przykładowy kod, który spowoduje powyższy błąd został przedstawiony poniżej. Zacznijmy od prostej klasy, którą chcemy zaprezentować na kontrolce GridView.
public class Data
{
  public int Id;
  public string Name;
}
Fragment kodu strony:
...
<asp:GridView ID="GridView1" runat="server">
</asp:GridView>
...
Kod zasilający kontrolkę jest równie prosty, dla uproszczenie nie korzystam ze źródła danych:
...
this.GridView1.DataSource = new Data[] { new Data { Id = 1, Name = "1" }, new Data { Id = 2, Name = "2" } };
this.GridView1.DataBind();
...
Uważny czytelnik mógł zauważyć, że w klasie Data użyłem publicznych pól składowych zamiast właściwości. I tu leży pies pogrzebany. Pola składowe klasy nie są automatycznie wykrywane i obsługiwane przy dowiązywaniu kontrolki (ang. binding). Innymi słowy pola trzeba opakować we właściwości. Nie wiem czy to przeoczenie w implementacji czy celowe działanie (biorąc pod uwagę treść komunikatu to drugie) ale w każdym razie ograniczenie to wspiera dobre praktyki programistyczne. Poprawny kod klasy Data powinien wyglądać tak:
public class Data
{
  public int Id { get; set; };
  public string Name { get; set; };
}

25/05/2009

Konkurs Code Camp 2009 - podsumowanie

Home

Na podsumowanie konferencji Code Camp 2009 w Warszawie z mojej perspektywy nadejdzie jeszcze czas. Najpierw chciałbym napisać o konkursie, który udało mi się zorganizować przy tej okazji. W skrócie konkurs składał się z dwóch etapów. Pierwszy etap (tzw. konkurs skojarzeniowy) odbył się w tygodniu poprzedzającym konferencję i polegał na odgadnięciu nazwy miejsca (kraju, miasta, wyspy itd.) na podstawie zdjęć, obrazków czy też opisu słownego. Wszystkie te miejsca miały tą wspólną cechę, że ich nazwy zostały użyte jako nazwy kodowe różnych produktów firmy Microsoft. Drugi etap, konkurs kartkowy, odbył się w czasie konferencji i polegał na odpowiedzeniu na 5 testowych pytań. Odpowiadając na pytanie nie można było korzystać z komputera, Internetu czyli trzeba było liczyć tylko na własną głowę.

Jak wyszło? Mówiąc szczerze nie jest do końca zadowolony. Frekwencja co tu dużo mówić nie dopisała. W konkursie wzięło udział tylko 6 osób. Chociaż z tego co dowiedziałem się od bardziej doświadczonych kolegów to i tak całkiem dobry wynik :)

Jeśli chodzi o konkurs skojarzeniowy to okazał się on dość łatwy i generalnie uczestnicy nie mieli problemów z odpowiedzeniem na pytania. Z konkursem kartkowym było już gorzej, poprawnych odpowiedzi było zdecydowanie mniej. Wydaje mi się, że było to spowodowane dwoma czynnikami. Po pierwsze pytania mogły być zbyt szczegółowe ale ja nie jestem tutaj obiektywny. Po drugie, konkurs był przewidziany na raptem 10 - 15 minut. Najwyraźniej nie udało mi się dostosować poziomu pytań do tak krótkiego czasu. Muszę o tym pamiętać i następnym razem przetestować pytania na znacznie większej grupie ludzi niż to uczyniłem.

Poniżej zamieszczam pytania i odpowiedzi do obu konkursów:

Konkurs skojarzeniowy


Odpowiedzi

  1. Dublin - Opis
  2. Hawaii - Visual Studio
  3. Astoria - ADO.NET Data Services
  4. Kilimanjaro - Sql Server 2010/Dynamics CRM 4.0
  5. Orleans - Opis
  6. Geneva - Opis
  7. Paris - Opis
  8. Ibiza - Sync Framework
  9. Oslo - Opis
  10. Quebec - Windows Embeded 2010
Do trzeciego pytani wkradł się błąd. Pytałem w nim o miasto na wschodnim wybrzeżu podczas gdy chodziło o zachodni brzeg USA. Z tego powodu zaliczałem również inne odpowiedzi na przykład Boston czyli Visual Studio 97.

Konkurs kartkowy

1. Mamy definicje dwóch klas jak poniżej:
class Base
{
   public virtual void Fun(int x)
   {
      Console.WriteLine("Base.Fun(int x)");
   }
}

class Derived : Base
{
   public override void Fun(int x)
   {
      Console.WriteLine("Derived.Fun(int x)");
   }

   public void Fun(object o)
   {
      Console.WriteLine("Derived.Fun(object o)");
   }
}
Co wypisze na ekran podany kod:
Derived d = new Derived();
d.Fun(1);
  1. Base.Fun(int x)
  2. Derived.Fun(int x)
  3. Derived.Fun(object o)
  4. Wywołanie metody spowoduje wyjątek
2.
Nullable<int> nullable = new Nullable<int>();
Type t = nullable.GetType();
Jaki rodzaj wyjątku zostanie rzucony po uruchomieniu tego kodu?
  1. Żaden
  2. InvalidOperationException
  3. NullReferenceExcpetion
  4. IndexOutOfRangeException
  5. ArgumentNullException
3. Załóżmy, że mamy zdefiniowaną zmienną o nazwie variable. Kiedy poniższy kod nie spowoduje rzucenia wyjątku:
variable = null;
variable.ToString();
4. Jaki będzie wynik wykonania poniższego kodu:
string s1 = new String(new char[] { 'a' });
string s2 = new String(new char[] { 'a' });
bool result = Object.ReferenceEquals(s1, s2);
Console.Write(result);

s1 = new String(new char[0]);
s2 = new String(new char[0]);
result = Object.ReferenceEquals(s1, s2);
Console.Write(result);

s1 = "Ala ma kota";
s2 = "Ala ma" + " kota";
result = Object.ReferenceEquals(s1, s2);
Console.Write(result);

s1 = "Ala ma kota";
string temp = "ma";
s2 = "Ala " + temp  + " kota";
result = Object.ReferenceEquals(s1, s2);
Console.Write(result);
  1. True, False, True, Talse
  2. False, False, True, False
  3. False, True, True, False
  4. False, False, False, False
  5. True, True, True, False
5. Jaki będzie wynik wykonania poniższego kodu:
delegate void Printer();
...
List printers = new List();
for (int i = 0; i < 4; i++)
{
  if (i % 2 == 0)
  {
      printers.Add(delegate { Console.Write(i); });
  }
  else
  {
     int j = i;
     printers.Add(delegate { Console.Write(j); });
  }
}

foreach (Printer printer in printers)
   printer();
  1. 0, 1, 2, 3
  2. 4, 4, 4, 4
  3. 4, 1, 4, 3
  4. 3, 2, 1, 0
  5. Inny

Odpowiedzi

  1. C - Derived.Fun(object o);

    W przypadku wywołania derived.Fun(1) zostanie wywołana metoda Derived.Foo(object). Metody zdefiniowane bezpośrednio na poziomie klasy wywołującej mają zawsze priorytet nad metodami zdefiniowanymi na poziomie klasy bazowej nawet jeżeli zostały przedefiniowane w klasie pochodnej. Jest tak ponieważ przy szukaniu metody najlepiej pasującej do wywołaniu, spośród wszystkich metod kandydujących odrzucane są metody zdefiniowane poza typem najbardziej wydziedziczonym. W wyniku tego kroku ze zbioru mogą zostać usunięte metody wirtualne.

  2. C - NullReferenceException

    Uruchomienie tego kod zakończy się rzuceniem wyjątku NullReferenceException w linijce z wywołaniem GetType. Typ Nullable<T> dostarcza własną implementacja ToString oraz GetHashCode ale nie GetType. Wywołanie GetType prowadzi więc do przekształcenia wartości Nullable<int> w referencje do obiektu (opakowywanie), a ponieważ w tym przypadku Nullable.HasValue jest równe false na stos odkładany jest null.

  3. Jeśli variable jest typem Nullable<T>

  4. C - False, True – bug?, True – optymalizacja kompilatora, False

  5. C - Domknięcie - Jak to działa?

19/05/2009

Name mangling, UniqueID, ClientID oraz ID

Home

Name mangling, po polsku maglowanie nazw, to w kontekście stron wzorcowych (ang. master pages) proces podmieniania identyfikatorów kontrolek przy generowaniu strony wynikowej (ze strony wzorcowej i strony z właściwą zawartością), a celem tej operacji jest zapewnianie, że identyfikatory będą na pewno unikalne w obrębie strony. Technicznie operacja ta sprowadza się do połączenia identyfikatora kontrolki z identyfikatorem kontenera w jakiej kontrolka została umieszczona, a dokładniej jego UniqueID. Operacja ta jest potrzebna ale stwarza też problemy i prowadzi niestety do błędów. Zacznę od opisu scenariusza:
  • Utworzyłem stronę wzorcową.
  • Utworzyłem stronę z zawartością.
  • Do strony z zawartością dodałem kontrolki GridView oraz SqlDataSource.
  • Skonfigurowałem połączenie z bazą danych.
  • Uruchomiłem stronę i GridView wyświetlił dane ze wskazanej tablicy.
  • Dodałem do strony kilka pól tekstowych i przycisk.
  • Dodałem metodę obsługującą naciśnięcie przycisku:
    protected void Button_Click(object sender, EventArgs e)
    {
     SqlDataSource.Insert();
    }
    
  • W kodzie strony deklaratywnie zdefiniowałem parametry wejściowe dla zapytania wstawiającego dane do bazy:
      <asp:SqlDataSource ID="SqlDataSource" runat="server" 
        ConnectionString="<%$ ConnectionStrings:Database %>" 
        SelectCommand="SELECT [Kolumna_1], [Kolumna_2], [Kolumna_3] FROM [Tabela]"
        InsertCommand="INSERT INTO users ([Kolumna_1], [Kolumna_2], [Kolumna_3]) VALUES (@Param_1,@Param1_2,@Param_3)">
      <InsertParameters>
        <asp:FormParameter Name="Param_1" FormField="PoleTekstowe_1" />
        ...
      </InsertParameters>
      </asp:SqlDataSource>
    
    W powyższym kodzie PoleTekstowe_1 to identyfikator pola tekstowego będącego źródłem danych dla parametru Param_1.
Mając gotową stronę uruchomiłem aplikację, wypełniłem pola tekstowe, nacisnąłem przycisk i otrzymałem wyjątek z komunikatem: Cannot insert the value NULL into column 'Kolumna_1'. Sprawdzam czy nie zrobiłem jakiejś literówki ale wszystko wygląda prawidłowo. Chwila zastanowienia i przypominam sobie, że przecież używałem już podobnego kodu i nie miałem problemów. Główkują dalej i zaczynam patrzeć podejrzliwie na użycie stron wzorcowych. Przenoszę więc cały kod do 'zwykłej' strony, odpalam i wszystko działa. W tym momencie przypominam sobie o Name mangling ale przecież nie zrezygnuję z tego powodu ze wszystkiego co dają strony tego typu.

Żeby nie przeciągać problem rozwiązałem rezygnując z deklaratywnego definiowana parametrów dla źródła danych na poziomie strony na rzecz zdefiniowania ich w kodzie. Oczywiście to nie wszystko. Sztuczka polega na tym, żeby przy podawaniu identyfikatora kontrolki, która będzie źródłem danych dla parametru należy użyć właściwości Control.UniqueID:
protected void Page_Load(object sender, EventArgs e)
{
   this.SqlDataSource.InsertParameters.Add(new FormParameter("Param_1", this.PoleTekstowe_1.UniqueID));
   this.SqlDataSource.InsertParameters.Add(new FormParameter("Param_2", this.PoleTekstowe_2.UniqueID));
   this.SqlDataSource.InsertParameters.Add(new FormParameter("Param_3", this.PoleTekstowe_3.UniqueID));
}
Control.UniqueID zawiera już przemaglowany identyfikator (globalnie unikalny w obrębie strony) i w związku z tym źródło danych nie ma problemu ze znalezieniem odpowiedniej kontrolki.

Dociekliwi mogą powiedzieć, że jest jeszcze właściwość Control.ClientID. Od razu mówię, że jeśli użyjemy tej właściwości to błąd również wystąpi. Na pierwszy rzut oka może wydawać się to dziwne ponieważ ClientID zawiera już zmieniony identyfikator. Różnica polega na tym, że do oddzielenie poszczególnych składowych identyfikatora ClientID używany jest inny separator niż przy UniqueID. Poniżej przykład dwóch identyfikatorów dla tej samej kontrolki:

UniqueID: ctl00$ContentPlaceHolder1$Nazwa
ClientID: ctl00_ContentPlaceHolder1_Nazwa

Dlaczego jednak użyto innych separatorów. Zacznijmy od tego, że aby proces podmieniania nazw był odwracalny ASP.NET musi umieć wydzielić z wygenerowanego identyfikatora jego składowe czyli identyfikatory kolejnych kontenerów i na końcu kontrolki. To jest przyczyna, dla której wprowadzono separatory. Separatorem powinien być oczywiście znak, który nie może wystąpić w bazowym identyfikatorze, w tym przypadku '$' ale można to zmienić.

Tutaj dochodzimy do momentu, w którym potrzebujemy z jakiegoś powodu odwołać się do wyrenderowanej kontrolki z poziomu JavaScript'u. Oczywiście potrzebujemy jakiś identyfikator i tu pojawiaja się problem. Otóż co zrobić jeśli jako separator użytu znaku niedozwolonego w identyfikatorach przez JavaScript? Problem ten postanowiono obejść wprowadzając ClientID i używając innego dozwolonego separatora czyli podkreślnika '_'. Reasumując UniqueID (renderuje się do atrybutu name tagu HTML) identyfikuje globalnie kontrolkę na potrzeby silnika ASP.NET, a ClientID (renderuje się do atrybutu id tagu HTML) na potrzeby JavaScript'u