Donnerstag, 29. Mai 2008

Seltsames Verhalten im Event Log

Zur Zeit schreibe ich, wenn es die Zeit und die Laune zulassen, an einem kleinen Tool zum Auslesen des Windows Event Log. Dabei habe ich eine recht interessante Feststellung gemacht.
Zum Auslesen der Einträge einer bestimmten Quelle, die im Event Log registriert ist, habe ich folgendes getan:

   1:  public static EventLogEntryCollection GetEntriesForSource( string logSource )
   2:  {
   3:      EventLog log;
   4:      EventLogEntryCollection entries;
   5:   
   6:      source = logSource;
   7:   
   8:      // EventLog vorbereiten
   9:      log = new EventLog();
  10:      log.Source = logSource;
  11:              
  12:      // EventLog-Einträge auslesen
  13:      entries = log.Entries;
  14:   
  15:      return entries;
  16:  }

Hier tue ich nichts anders, als ein EventLog-Objekt zu erstellen, das, wer hätte es gedacht, den Zugriff auf das Windows Event Log erlaubt.
Anschließend wird die Quelle (Source) gesetzt, deren Einträge ausgelesen werden sollen. Damit ist die Vorbereitung auch schon abgeschlossen und die Einträge innerhalb der Source können in eine EventLogEntryCollection eingelesen werden.

Damit ist das Auslesen aus der gewünschten Quelle auch schon abgeschlossen - dachte ich. Seltsamerweise werden trotz des Setzens der zu lesenden Quelle alle Einträge des Logs, in dem sich die Quelle befindet, in die EventLogEntryCollection geschrieben.

Ich möchte jetzt nicht so weit gehen zu sagen, dass es sich hierbei um einen Fehler im Konzept des Event Log-Handlings handelt, aber für mich ist es nicht logisch, alle Einträge in meine Collection geschrieben werden, obwohl ich - für meine Begriffe - eine Eingrenzung auf eine ganz bestimmte Source vorgenommen habe.

Das Schlimme daran ist nicht die Tatsache an sich, sondern viel eher, dass man sie nicht direkt wahrnimmt. Ich habe beispielsweise in eigenen Tests nie zwei mal hintereinander Quellen aus dem gleichen Log verwendet, so dass mir nie aufgefallen ist, dass alle Quellen des Logs exakt gleich viele (und vor allem die selben) Einträge hat. Das hat erst ein Betatester festgestellt (vielen Dank dafür an tropensturm!).

Die Beseitigung des Problems gestaltet sich glücklicherweise denkbar einfach, so dass in meiner Anzeige jetzt tatsächlich nur noch die erwarteten Einträge zu finden sind. Hierfür wird einfach nachträglich nochmals die Source eingegrenzt.

   1:  if( !entry.Source.Equals( source ) )

Sonntag, 18. Mai 2008

HowTo: DateTimes sortieren im datengebundenen DataGridView

Ich hatte letztens die Anforderung, die Daten eines DataGridView, nach einem Datumswert zu sortieren. Die Daten innerhalb des DataGridView kamen hierbei aus einer Datenbindung an ein DataSet.
Das Sortieren an sich ist hierbei kein Problem, folgender Aufruf ist völlig ausreichend:

   1:  myDataGridView.Sort( myDataGridView.Columns[2], System.ComponentModel.ListSortDirection.Descending);

Bei meinen Datumswerten sah das zuerst auch sehr gut aus, jedenfalls bis zu dem Zeitpunkt, zu dem mir aufgefallen ist, dass der 28.04. weit vor dem 08.05. liegt, obwohl ich ja eigentlich absteigend sortiert habe. Das liegt ganz einfach daran, dass die Daten innerhalb der Spalte als String behandelt und demnach auch als solcher sortiert werden. Hierfür ist das Verhalten absolut korrekt, für meine Zwecke jedoch absolut falsch.

Es muss also irgendwie möglich sein, dem DataGridView mitzuteilen, dass diese Spalte keine Strings enthält, sondern DateTimes. Allerdings ist dies nicht direkt am DataGridView möglich, da zur Design-Zeit für dieses ja noch gar nicht fest steht, welche Daten, geschweige denn in welcher Reihenfolge die Daten vorliegen.

Demnach muss diese Einstellung direkt an der Datenquelle, hier am DataSet, getroffen werden. Auch das ist glücklicherweise mit nur einer Zeile Code zu machen.

   1:  myDataSet.Tables[0].Columns[2].DataType = typeof(DateTime);

Nun liefert die Sortierung der Datumswerte innerhalb des DataGridViews tatsächlich das gewünschte Ergebnis.

Das gleiche Verfahren kann übrigens angewandt werden, wenn innerhalb des DataGridView Bilder angezeigt werden sollen. Normalerweise hätte man nämlich den Namen der verwendeten Klasse - also System.Drawing.Bitmap - in der Spalte stehen, was natürlich nicht wirklich sinnvoll ist. Der folgende Aufruf sorgt dafür, dass das in der Zeile abgelegte Bild korrekt angezeigt wird.

   1:  myDataSet.Tables[0].Columns[0].DataType = typeof(Bitmap);

So klappt es dann auch mit der Anzeige von Bildern.

Donnerstag, 15. Mai 2008

Inline C# for dummys...

Bei einer Transformation (BizTalkMapping (.btm)) in einem Bizalk Projekt, hat man die Möglichkeit mit Hilfe des "Scripting" Functioniden C# (oder besser "Inline C#") einzusetzen. Vielerorts sehr hilfreich, da man etwas speziellere Mappings damit schnell implementieren kann.

Beispiel:

   1:  public string ConvertStringToNumber(string strInput)
   2:  {
   3:      if(strInput.Equals("string1"))
   4:          return "1";
   5:      else  if(strInput.Equals("string2"))
   6:          return "2";
   7:   
   8:      return("0");
   9:  }

Benutzt man jetzt aber innerhalb EINER Transformation mehrere "Scripting" Functionide, gibt es eine Eigenart die einem eine schöne Zeit mit Fehlersuche bescherren kann, wenn man diese Eigenart nicht kennt.

Nehmen wir als Beispielt an, dass der Code von oben in unserem ersten Functionied eingebettet ist. Ziehen wir jetzt einen neuen "Scripting" Functionied in unsere Transformation rein und coden dort im "Inline C#" folgenden Code hinein:

   1:  public string ConvertStringToNumber(string strInput)
   2:  {
   3:      if(strInput.Equals("string3"))
   4:          return "3";
   5:      else  if(strInput.Equals("string4"))
   6:          return "4";
   7:   
   8:      return("5");
   9:  }

Wir sehen das Ergebniss soll beim ersten Functionied 0, 1 oder 2 und beim zweiten 3, 4 oder 5 sein. Gleich vorweg, es wird nicht korrekt funktionieren.

Der "Fehler" liegt einfach daran das intern die Methoden anhand des METHODENNAMEN nur ein mal angelegt werden. Und da unsere beiden "Inline C#" Methoden den gleichen Namen haben, wird in beiden Fällen die SELBE Methode benuzt (im Normalfall die zuerst angelegte). Das perfide daran ist, da es einfach KEINE Fehlermeldung beim Übersetzen oder Ausführen des Projektes gibt. Es läuft alles durch, es wird aber eben in beiden Fällen die selbe Methode aufgerufen.

Abhilfe schafft das unterschiedliche Vergeben des Methodennamens für die Methoden in den verschiedenen "Scripting" Functionieden. Also sollet unser zweites Codebeispiel so aussehen damit alles funktioniert:

   1:  public string ConvertAnotherStringToNumber(string strInput)
   2:  {
   3:      if(strInput.Equals("string3"))
   4:          return "3";
   5:      else  if(strInput.Equals("string4"))
   6:          return "4";
   7:   
   8:      return("5");
   9:  }

Mittwoch, 14. Mai 2008

Custom Pipeline Components Wizard, Toolbox Probleme und Testen

Martijn Hoogendoorn hat einen Wizard geschrieben, der einem viel programmierarbeit abnimmt beim erstellen von custom pipeline components: http://www.codeplex.com/btsplcw

Einfach den Source Code compilieren und im Visualstudio kann man mit einem Rechtsklick auf das Installer Projekt gleich "install" auswählen um den Wizard zu integrieren.

Legt man ein neues Projekt an kann man nun unter BizTalk Projects den BizTalk Server Pipeline Component Wizard auswählen. Dieser erzeugt einem dann ein C# (auf Wunsch auch VB.NET) Projekt wo man nur noch fertig implementieren muss wo ein #error Compilerflag gesetzt wurde.


Ich weiß nicht ob es nur so mir ging, aber ich fand es nicht eindeutig wie man denn nun eine selbst geschriebene Komponente in die Pipeline "hineinbekommt". Alles was ich gefunden habe lief ungefähr immer auf das Selbe hinaus, "einfach reinziehen in die Toolbox" ...das hatte jedenfalls nicht so funktioniert wie ich mir das anfangs dachte ;)

Ersteinmal muss man ein paar Vorraussetzungen schaffen damit es später keine Probleme beim deployen gibt:

1.) Die Komponente muss mit einem key signed sein

2.) Die Komponente sollte im GAC installiert werden

Hat man das muss man in der Toolbox der Pipeline Datei mit Rechtsklick das Menü holen und dort "Choose Items..." auswählen und dort die neue Komponente auswählen. Danach ist sie endlich auch für unsere Custom Pipeline verwendbar.


Zum Testen von Pipelines gibt es übrigens ein nützliches Werkzeug unter "...\Microsoft BizTalk Server 2006\SDK\Utilities\PipelineTools\Pipeline.exe". Damit kann man die .btp Datei direkt oder die .NET Assembly testen ohne zu deployen!!

Montag, 12. Mai 2008

Bugzilla VM

Bugtrackingtools sind bei einem professionellen Einsatz von Softwareenticklung im Team eine nicht zu unterschätzende Hilfe. Wer sich schnell und ohne Probleme solch ein Tool aufsetzen möchte, kann unter angegebenem Link eine VM (Virtuelle Maschine für VMWare) mit vorinstalliertem Bugzilla herunterladen. Die VM braucht ca.450MB auf der Festplatte und nur 128MB des Hauptspeichers. Es sollte also auch auf langsameren Rechnern kein Problem sein, diese nebenher laufen zu lassen.

Download: Bugzilla VM

DataGridView - Vorderste Spalte ausblenden

Heute beschäftigen wir uns mal mit etwas, das eigentlich sehr einfach ist, mich allerdings doch etwas aufgehalten hat, weil einfach das Wissen gefehlt hat. Damit dies nicht wieder passiert, wird hier gebloggt! ;)

Die Aufgabenstellung ist eine extrem einfache:
Verwendet man ein DataGridView zur Anzeige von Daten (z.B. via DataBinding auf ein DataSet), wird standardmäßig ganz links eine Spalte angezeigt, die es ermöglicht, die komplette Zeile zu markieren und durch einen kleinen Pfeil anzeigt, dass eine Spalte die aktuelle ist.
Nun ist in meinem Fall der DataGridView so eingestellt, dass immer die ganze Zeile markiert wird, egal, wo sie angeklickt wird. Daraus folgt, dass wir die besagte erste Spalte nicht benötigen.

Naheliegend wäre es nun ja, dass diese Spalte in der Columns-Collection des DataGridView enthalten ist und sie mit dem Aufruf

   1:  myDataGridView.Columns[0].Visible = false;

unsere Anforderung erfüllt.
Startet man jetzt allerdings die Applikation, wird man feststellen, dass statt der nutzlosen Spalte ganz links die erste eigene Spalte ausgeblendet wird, die unsere Daten anzeigen sollte - so geht es also nicht!

Statt dessen bietet das DataGridView eine Eigenschaft, die festlegt, ob besagte Spalte angezeigt wird oder nicht: DataGridView.RowHeadersVisible
Die Spalte, um die es hier geht, wird also offensichtlich als RowHeader bezeichnet. Reichlich interessant, wie ich finde. Zu meiner Schande muss ich jedoch gestehen, dass ich darauf nie im Leben gekommen wäre. Zum Glück gibt es ja das Internet.

Der korrekte Aufruf zum Ausblenden der ersten Spalte sieht dann also letztendlich so aus:

   1:  myDataGridView.RowHeadersVisible = false;

Die Eigenschaft kann aber natürlich auch einfach im Eigenschaften-Fenster des Designers gesetzt werden.

Problem gelöst, ich bin glücklich und meine GUI gefällt mir wieder. Daumen hoch ;)

Donnerstag, 8. Mai 2008

#error "Errors exist for one or more children."

Ich hatte gerade ein seltsames Problem mit einer Orchestration innerhalb eines VisualStudio BizTalk.Projektes. Ich konnte einfach partout nicht mehr kompilieren, obwohl im Orchestration-Designer alles bestens aussah (der Fehler war in meinem Fall '#error "Errors exist for one or more children."').

Nachdem ich alles was mir eingefallen ist ausprobiert hatte, öffnete ich die Orchestration im XML-Editor (eine Orchestration ist ja nichts weiter als ein XML). Der "normale" Aufbau einer Orchestration folgt folgendem Muster:

   1:  #if __DESIGNER_DATA
   2:   #error Do not define __DESIGNER_DATA.
   3:   <?xml version="1.0" encoding="utf-8" standalone="yes"?>
   4:   <om:MetaModel [...viele Attribute...]>
   5:    [...viel mehr XML...]
   6:   </om:MetaModel>
   7:  #endif // __DESIGNER_DATA

Manchmal findet man jedoch darunter noch Code im folgendem Stil:

   1:  [Microsoft.XLANGs.BaseTypes.BPELExportable(false)]
   2:  module xyz.IndustryTemplate.MSCRMToSAP
   3:  {
   4:   [...viel mehr Code]
   5:  }

In meinem Fall hat es nun geholfen den untenstehenden Code komplett zu löschen und die Orchestration zu speichern. Der erneuter Kompilierungsversucht ist danach fehlerfrei durchgelaufen.

Codepages und DIN 66003

Es gibt Dinge, die wollte ich nie wissen und habe es tunlichst vermieden mich damit auseinanderzusetzen, jetzt hat mich das dank der Anbindung an verschiedene UNIX Systeme doch wieder eingeholt: Zeichsatzcodierungen.

Mit Hilfe von BizTalk werden verschiedene Quellen ausgelesen, die UNIX-typisch jeweils anders codiert sind. Sehr geholfen hat mir http://msdn.microsoft.com/en-us/library/ms776446(VS.85).aspx, jedoch ist das auch hier nicht ganz ohne Fallstricke ausgegangen:
IBM 437 = 437, ISO 8859-1 = 28591, ISO 8859-15 = 28605, und DIN 66003?

urks, steht nicht drin, ist ja auch klar. Die Recherche ergab ziemlich schnell, dass dahinter ISO 646 in der deutschen Ausprägung steht (021, bzw. DE, je nach Bezeichnungsvariante), was nichts anderes als ASCII 7bit für Deutschland darstellt... tzia nur das findet man immer noch nicht in dem obigen MSDN Link. Erst weitere Nachforschungen haben eine Implementation der ISO 646 nach IA5 zu Tage gefördert, BINGO! Da gibts tatsächlich was dazu unter der MSDN:

DIN 66003 = 20106

Letztendlich wird mir wieder klar, warum ich diese Zeichensatzcodierungen nie ausstehen konnte, die blanke Flut an Bezeichnern für ein und dasselbe sind schon ziemliche irre für sich allein genommen...

Ich hab aber auch eine Serie :-/

BizTalk: "Variable Längen" handeln in Flatfile Schemas

Ziemlich viel Zeit hat mich folgendes Problem mit BizTalk gekostet, als ich die Lösung hatte hab ich dazu auch endlich was in Google gefunden... ärgerlich.

Folgendes Problem: ich bekomme eine Datenstruktur mit folgendem Aufbau zugeschickt: nnnXXXXabcd/CR/LF
Dies ist erst einmal relativ simpel mit dem Flatfile Wizard von Biztalk zu lösen (wem das Tutorial aus der MSDN nicht den zündenen Geistesblitz gibt, sollte es mit dem hier versuchen).

Jetzt ist allerdings folgender Fallstrick in meinem Flatfile, eine nachfolgende row im Flatfile kann wie folgt aussehen:
nnnXXXXabcdefgh/CR/LF
urgs, richtig! Variable Längen... in einem positional record, das gibt einem auch prompt beim ausprobieren einen "Unexpected Data found while looking for '/r/n'" Error

Um das in den Griff zu kriegen, mußte ich furchtbar lange rumpobieren da ich nix sinnvolles an Hilfe fand. Ich bin dann mit dem XSD Editor auf folgendes gestoßen:

   1:  <?xml version="1.0" encoding="utf-16" ?>
   2:    <xs:schema xmlns:b="http://schemas.microsoft.com/BizTalk/2003"
   3:        xmlns="http://xyz.de"
   4:        targetNamespace="http://xyz.de"
   5:        xmlns:xs="http://www.w3.org/2001/XMLSchema">
   6:    <xs:annotation>
   7:    <xs:appinfo>
   8:      <schemaEditorExtension:schemaInfo namespaceAlias="b"
   9:        extensionClass="Microsoft.BizTalk.FlatFileExtension.FlatFileExtension"
  10:        standardName="Flat File"
  11:        xmlns:schemaEditorExtension="http://schemas.microsoft.com/BizTalk/2003/SchemaEditorExtensions" />
  12:      <b:schemaInfo standard="Flat File"
  13:        codepage="65001"
  14:        count_positions_by_byte="true"
  15:        default_pad_char="" pad_char_type="char"
  16:        parser_optimization="speed"
  17:        lookahead_depth="3"
  18:        suppress_empty_nodes="false"
  19:        generate_empty_nodes="true"
  20:        allow_early_termination="false" 
  21:        early_terminate_optional_fields="false"
  22:        allow_message_breakup_of_infix_root="false"
  23:        compile_parse_tables="false"
  24:        root_reference="Group" />
  25:      </xs:appinfo>
  26:    </xs:annotation>
  27:   
  28:  ...

Das Attribute "allow_early_termination" auf true gesetzt und dann bei pos_length im entsprechenden Element eine geeignete Größe gewählt (z.B. pos_length="1000") führte zu einem funktionierendem Ergebnis.

Nachteil bleibt der Faktor mit pos_length, sollte es einmal doch "länger" sein, sind wir wirklich aufgeschmissen an diesem Punkt. Da bleibt dann wohl nur noch ein Umweg dies über die BizTalk Pipelines zu fixen, bzw. mit einer geeigneten Applikation, die uns das File vorher aufbereitet.

Mittwoch, 7. Mai 2008

Das Windows Event Log und seine Tücken

Logging ist in der professionellen Softwareentwicklung ein wichtiges Thema. Nicht umsonst gibt es eigens für das Logging eigene Frameworks wie beispielsweise das sehr gute log4net.
Dieses bietet verschiedenste Appender an, die festlegen, wie und wohin geloggt werden soll. So haben wir unter anderem die Auswahl zwischen dem Schreiben von Einträgen in Datenbanken, Textfiles, die Konsole und das Windows Event Log.
Gerade die letzte Möglichkeit bietet sich - aus meiner Sicht - besonders an, denn sie hat einen großen Vorteil: Jeder System-Administrator der Welt ist (sofern er Windows-Systeme administriert) mit dem Windows Event Log vertraut und weiß, wie er an seine Einträge heran kommt. Sicherlich, es ist auch nicht wirklich komplex, Textfiles, die an einer fest definierten Stelle im System abgelegt werden, zu analysieren, doch warum sollte man dem armen Admin zumuten, noch eine zweite Stelle zu prüfen?

Ein weiterer Vorteil des Loggens in das Windows Event Log: .NET bietet uns schon alles, was dazu nötig ist.
Die Klasse  EventLog beinhaltet tatsächlich alles, was wir brauchen und ist zudem sehr einfach zu verwenden. Nachfolgend mal ein kleiner Code-Schnipsel, der zeigt, wie man ein eigenes Log mit eigener Quelle erstellt und anschließend einen Eintrag dort hinterlegt.

   1:  using System;
   2:  using System.Diagnostics;
   3:   
   4:  namespace BBoSoft.EventLogDemo
   5:  {
   6:      public class EventLogDemo
   7:      {
   8:          [STAThread]
   9:          public static void Main()
  10:          {
  11:              // pruefen, ob die Quelle bereits existiert
  12:              if(!EventLog.SourceExists("DemoSource"))
  13:              {
  14:                  // Source anlegen
  15:                  // der erste Parameter ist die Quelle, in die geschrieben werden soll
  16:                  // der zweite Parameter ist das Log, dem die Quelle hinzugefuegt werden soll
  17:                  EventLog.CreateEventSource("DemoSource", "DemoLog");
  18:              }
  19:   
  20:              EventLog.WriteEntry("DemoSource", "DemoMessage", EventLogEntryType.Information, 150, 12);
  21:          }
  22:      }
  23:  }

Wie hier zu sehen ist, ist das Schreiben eines Eintrags in das Event Log absolut einfach zu erledigen.
Beim Aufruf von EventLog.WriteEntry() werden, für diese Überladung, fünf Parameter übergeben. Zuerst die Quelle, dann die Nachricht des Events, der Typ (Information, Warnung, Fehler), eine selbst gewählte EventId sowie eine selbst gewählte Kategorie.

Lässt man den Code so laufen und schaut in das EventLog, müsste das in etwa so aussehen:

EventLog1

Das hat also wunderbar funktioniert. Erfreulich.
Wer jetzt noch den Titel dieses Postings im Kopf hat, wird sich sicherlich fragen, wo denn hier nun die Tücken liegen. Um das zu demonstrieren, ist es nötig, zuerst einmal die erstellte Source oder am Besten gleich das gesamte Log zu entfernen. Hierzu öffnen wir den Registrierungseditor und navigieren zum Punkt HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Eventlog und löschen dort dein Eintrag DemoLog.

Zurück zum Code:
Fügen wir doch mal ein beliebiges Sonderzeichen - in meinem Fall war es ein '/' - in den Namen des Logs ein und führen sie das Programm erneut aus. Sieht das EventLog jetzt nicht richtig interessant aus? ;)

Was haben wir jetzt also gelernt?
Richtig! Sonderzeichen im Namen des Logs sind BÖSE! Ich hab es auf die harte Tour gelernt und hoffe, einigen geholfen zu haben, nicht in die gleiche Falle zu tappen.

Noch ein wichtiger Nachtrag:
Zum Anlegen der Source werden Administratorrechte für das System benötigt, da hier in die Registrierung geschrieben wird.

Witzig bis nützliche MD5-Seite

Unter http://md5.rednoize.com/ kann man sich recht einfach Texte mit MD5 codieren und decodieren lassen. Nicht nur von Zeit zu Zeit ganz brauchbar sondern auch in der Aufmachung eine witzige Idee.
Daran angelehnt habe ich mir die Mühe gemacht, und mal eine erste Version eines SearchPlugins für Firefox erstellt. Wie üblich: Nutzung auf eigene Gefahr, keine Gewährleistung, Garantie, Rücknahme o.ä. Updates vorbehalten.
Zu finden hier

Dienstag, 6. Mai 2008

Sara Ford's VS 2008 Tipp of the day

Eine sehr interessante Sache sind die Tipps of the day für Visual Studio in der 2008er Version, die Sara Ford in ihrem Blog tagtäglich veröffentlicht. Da stolpert man mit Sicherheit über viele sehr nützliche Dinge, an die man im Traum nicht gedacht hätte.

Vielen Dank für den Tipp an Dariusz Parys.