Buchtipp: „The design of everyday things“

Zurzeit lese ich ein Buch über das Design von Gebrauchsgegenständen:

The Design of Everyday Things

the-design-of-everyday-things

(Vorsicht, ein Amazon-Partnerlink!)

In dem Buch werden ganz viele tolle Beispiele gebracht, warum etwas funktioniert und warum nicht.

Z. B. eine Drehtüre. Oder ein Telefon, ein Autoradio, Salz- und Pfefferstreuer, usw.

Ich verspreche mir von dem Buch dass ich benutzbarere Software schreibe die intuitiver ist und dem Benutzer hilft schneller eine Aufgabe zu lösen.

Der Autor hat übrigens seinerzeit mit dem genialen Jakob Nielsen die „Norman Nielsen Group“ gegründet. Also schon deshalb ein toller Typ 🙂

Error 5000 on event log when starting a .NET 2.0 Windows Forms application

Recently we updated a .NET 2.0 Windows Forms application on a client’s site.

The updated version starts well from a local drive but failed to start on a remote server via Citrix.

Error

Whenever we started the application it simply did not show up any window while displaying an error in the event log (in German):

Ereignistyp: Fehler
Ereignisquelle: .NET Runtime 2.0 Error Reporting
Ereigniskategorie: Keine
Ereigniskennung: 5000
Datum: 24.11.2009
Zeit: 17:20:28
Benutzer: Nicht zutreffend
Computer: MYSERVER
Beschreibung: EventType clr20r3, P1 myapplication.exe, P2 1.0.3615.30626, P3 4b0c0335, P4 system.configuration, P5 2.0.0.0, P6 4889de74, P7 1a6, P8 15a, P9 ioibmurhynrxkw0zxkyrvfn0boyyufow, P10 NIL.

The event log entry description points to the System.Configuration namespace, so it is likely that the issue was somehow related to the application configuration file „app.config“ (respectively „myapplication.exe.config“).

Steps to locate the issue

We isolated the issue by starting with a (nearly) empty „app.config“ file with which the application succeeded to start. We then added section by section back to the configuration file, until the error occurred again.

Cause

The bottom line was that a custom configuration section handler that worked well when being used in the locally started application did not work when being run from the Citrix server. A likely cause is that the Citrix server is a 64 bit machine whereas the other machine is a 32 bit machine.

Resolution

To resolve the issue we removed the custom configuration section handler and worked around it in code.

Den service-bw-SOAP-Webdienst von PHP aus aufrufen

Vor Kurzem waren wir in der glücklichen Lage, dass wir auf das Portal von service-bw.de programmatisch via SOAP/WSDL zugreifen durften.

In .NET war das dank dem automatischen Proxy-Generierungs-Werkzeug von Visual Studio.NET 2008 sehr einfach.

In PHP war es etwas trickreicher. Danke viel Ausprobieren und vor allem dank diesem Posting konnten wir es lösen.

Um ein Bisschen was „ins Internet“ zurückzugeben, stelle ich nachfolgend zwei kurze Beispiele vor.

Beispiel 1, Aufruf der SOAP-Funktion getLebenslageTree()

Folgendes Beispiel ruft eine Funktion des Lebenslagen-Webdienst auf. Es wird gezeigt wie mehrere Parameter die auch komplexe Typen enthalten, korrekt übergeben werden.

$client = new SoapClient(
    'https://www.service-bw.de/zfinder-core-ws/lebenslage?wsdl');

$profilData = new stdClass();
$profilData->ags = "0";
$profilData->plz = "0";
$profilData->sprache = "deu";
$profilData->kategorienUndVerknuepfung = false;

$pkey = new stdClass();
$pkey->id = 93256;
$pkey->mandantenId = 0;
$pkey->sprachId = "deu";

$param = new stdClass();
$param->lebenslageKey = $pkey;
$param->kategorieIds = array(783160, 783161);
$param->mandantenIds = array(0);
$param->operatorAnd = false;
$param->profilData = $profilData;

$result = $client->getLebenslageTree($param);

Der folgende Download enthält den vollständigen Quelltext des Beispiels:

Download Beispiel 1

Beispiel 2, Aufruf der SOAP-Funktion getStichwoerterAndSynonymeByNamePrefix()

In diesem Beispiel wird eine Funktion des Stichwort-Webdienst aufgerufen. Es werden erneut komplexe Parameter, inklusive Felder („Arrays“) an die Funktion übergeben.

$client = new SoapClient(
    'https://www.service-bw.de/zfinder-core-ws/stichwort?wsdl');

$searchParameters = new stdClass();
$searchParameters->alphaBlockModus = false;
$searchParameters->hitRestriction = false;
$searchParameters->numberOfHits = 100;
$searchParameters->regionIds = array(0);
$searchParameters->mandantenIds = array(0);
$searchParameters->sprachId = "deu";
$searchParameters->value = "A";

$profilData = new stdClass();
$profilData->ags = "0";
$profilData->plz = "0";
$profilData->sprache = "deu";
$profilData->kategorienUndVerknuepfung = false;

$param = new stdClass();
$param->searchParameters = $searchParameters;
$param->profilData = $profilData;

$result = $client->getStichwoerterAndSynonymeByNamePrefix($param);

Der folgende Download enthält wiederum den vollständigen Quelltext des Beispiels:

Download Beispiel 2

Ich wünsche allen Entwicklern viel Erfolg und freue mich, falls ich ein Bisschen weiterhelfen kann! Rückmeldung wie immer willkommen 🙂

XCOPY/ROBOCOPY like class in C#

During the process of moving some command scripts to a more robust solution that involved C# Script, I was in the need to replace an XCOPY call with an equivalent in C#.

What I wanted to avoid is to simply start a CMD.EXE process and call the XCOPY command.

Instead I looked for a class/function in .NET/C# that can serve as a replacement. Although I found some solutions, none of them worked the way I wanted them.

Bring your own code

So I decided to develop my own small solutions that is no rocket science code but fulfills my requirements.

It has fewer features as XCOPY and is intended for copying folders (the original XCOPY works for both files and folders). In addition it is being designed to be used in a console environment (i.e. the operations are synchronously and blocking).

An example call looks like:

var options =
    new ZetaFolderXCopyOptions
        {
            FilesPattern = "*.*",
            RecurseFolders = true,
            CopyEmptyFolders = true,
            CopyHiddenAndSystemFiles = true,
            OverwriteExistingFiles = true,
            CopyOnlyIfSourceIsNewer = false,
            FoldersPattern = "*"
        }
        .AddExcludeSubStrings(
            "\\.svn\\",
            "\\_svn\\",
            "\\_Temporary\\" );

var xc = new ZetaFolderXCopy();
xc.Copy(
    sourceFolderPath,
    destinationFolderPath,
    options );

The full code consists of a single .CS file and can be downloaded from here for free.

Hopefully this code is of some basic usage to you, keep the feedback coming!

Update 2015-11-25

I’ve uploaded a greatly enhanced version which adds wildcard and Regex exclude support  as well as retry options.

This is another step towards a native C#/.net version of robocopy.

Creating an XtraReports report that works both with Microsoft SQL Server and VistaDB

Currently I am extending and enhancing the reporting system for our test management tool Zeta Test.

In this article I will describe the issues I discovered (and solved) when trying to design one single report that works with both Microsoft SQL Server and VistaDB.

Creating a report

I decided to go with XtraReports from DevExpress as the reporting system.

The basic design steps creating and deploying a report with XtraReports is similar to methods when working with e.g. Microsoft SQL Server Reporting Services:

  1. Create a new report in the designer of XtraReports.
  2. Specify connection string/settings (using the SqlClient namespace classes).
  3. Specify tables/SQL queries to use as the data source for the report.
  4. Design your report graphically.
  5. Save the report to a .REPX file.
  6. Pack the .REPX file into the deployment setup.
  7. Ship the setup to the customers.

Displaying a report

In Zeta Test I had to perform the following steps when displaying a report:

  1. Load the report.
  2. Replace the design-time connection string by the connection string to the currently used database of the currently loaded project in Zeta Test.
  3. Display the report in a print-preview form.

Two databases

Since a project in Zeta Test can either use Microsoft SQL Server or VistaDB as the back-end database, I wanted to design a single report so that it can be used with both back-end databases.

Inially I planned to simply design the reports with an OleDB connection to the SQL Server database and then later when displaying the report for a VistaDB database, simply switch the connection string to the VistaDB database.

Unfortunately there is no OleDB provider for VistaDB. Therefore this solution did not work. Another idea was to simply leave the SqlClient classes that were used when designing the report as they are, only changing the connection string. This did not work, either.

So I did come up with another solution that works quite well as of writing this article:

Rewriting the .REPX report

A .REPX report file is a C# source code file that gets compiled when loading the report with the XtraReport.LoadLayout method.

I decided to give the following „algorithm“ a try:

  1. Load a .REPX file that was designed with the SqlClient classes as a string into memory.
  2. Do some string replacements to replace all SqlClient classes by their VistaDB counterparts.
  3. Call XtraReport.LoadLayout on the replaced string.
  4. Display the report in a print-preview form.

Luckily, after some try and error I actually was able to get this working!

In the following chapter I outline some of the methods I created in order to hopefully give other developers some ideas to use in their own applications:

Code to rewrite a .REPX report

The following method is the main method that is being called in order to load and display a report. It is a member of the print-preview form:

public void Initialize(
    FileInfo reportFilePath,
    Pair<string, string>[] parameters)
{
    var report = new XtraReport();

    if (Host.Current.CurrentProject.Settings.IsVistaDBDatabase)
    {
        var rawContent = UnicodeString.ReadTextFile(reportFilePath);

        var replacedContent = replaceSqlClientForVistaDB(rawContent);

        using (var ms = new MemoryStream(
            Encoding.Default.GetBytes(replacedContent)))
        {
            report.LoadLayout(ms);
        }
    }
    else
    {
        // Assumes that the report was designed with SQL Server.
        // If not, one would have to replace the other way, i.e.
        // from VistaDB to SQL Server.
        // I am leaving this out until really needed.
        report.LoadLayout(reportFilePath.FullName);
    }

    // --
    // Change connection string.

    var da = report.DataAdapter as DbDataAdapter;

    if (da != null)
    {
        adjustConnectionString(da.SelectCommand);
        adjustConnectionString(da.InsertCommand);
        adjustConnectionString(da.DeleteCommand);
        adjustConnectionString(da.UpdateCommand);
    }

    // --
    // Adjust parameters.

    if (parameters != null && parameters.Length > 0)
    {
        foreach (var parameter in parameters)
        {
            if (containsParameter(report.Parameters, parameter.First))
            {
                report.Parameters[parameter.First].Value = parameter.Second;
            }
        }
    }

    // --
    // Bind the report's printing system to the print control.

    printControl.PrintingSystem = report.PrintingSystem;

    // Generate the report's print document.
    report.CreateDocument();
}

The actual method to replace the string is the following:

private static string replaceSqlClientForVistaDB(string content)
{
    if (string.IsNullOrEmpty(content))
    {
        return content;
    }
    else
    {
        var result = content;

        // Add reference.
        result =
            result.Replace(
                Environment.NewLine + @"///   </References>",
                string.Format(
                    Environment.NewLine + @"///     <Reference Path=""{0}"" />" +
                    Environment.NewLine + @"///   </References>",
                    typeof (VistaDB.IVistaDBValue).Assembly.Location
                    ));

        // Change class and namespace names.
        result =
            result.Replace(
                @"System.Data.SqlClient.Sql",
                @"VistaDB.Provider.VistaDB");

        // Comment out non-available VistaDB properties.
        result = Regex.Replace(
            result,
            @"(^.*?FireInfoMessageEventOnUserErrors.*?$)",
            @"//$1",
            RegexOptions.Multiline);

        return result;
    }
}

And the method to replace the connection string is defined as follows:

private static void adjustConnectionString(DbCommand command)
{
    if (command != null)
    {
        var conn = command.Connection;

        if (conn != null)
        {
            if (conn.State == ConnectionState.Open)
            {
                conn.Close();
            }

            conn.ConnectionString = getConnectionString();
        }
    }
}

Other methods are left out for simplicity. If you want to see them, simply drop me a note, e.g. by e-mail.

Epilog

In this short article I’ve shown you a way to modify an XtraReports report during runtime of your application to work with both a Microsoft SQL Server and a VistaDB database without the need to design separate reports.

As of writing this article the reports I tested work quite well, probably/possible I will have to do further adjustments when getting to more complex reports. I will update the article then.

I am looking forward for lot of feedback! 🙂

Zeta Test 1.0 ist da!

Hurra! Nach 1½ Jahren Entwicklungszeit ist Version 1.0 unserer Test-Management-Umgebung „Zeta Test“ nun verfügbar.

Das, zugegebenermaßen erklärungsbedürftige, Produkt ist hervorragend geeignet um Testpläne und Testfälle für Software-Tests zu erstellen.

Kurzer Auszug aus der Website zum Produkt:

Zeta Test ist eine integrierte Test-Management-Umgebung, mit der Sie Black-Box-Tests, White-Box-Tests, Regressionstests und Change-Management-Tests (Release-Wechsel) von Software durchführen können.

Zeta Test unterstützt Sie dabei bei der Planung, Durchführung, Monitoring, Protokollierung und Dokumentation der Tests sowie beim Auswerten der Ergebnisse.

Schreiben und verwalten Sie mit Zeta Test Ihre Testfälle, Testpläne und Vorlagen. Testen Sie Ihre Software nach Testdrehbüchern, die Sie mit Zeta Test erstellt haben.

Ich bin gespannt wie ein Flitzebogen, wie die Software einschlägt! Alle Beta-Tester waren hellauf begeistert, jetzt heißt es den Vertrieb ordentlich anlaufen zu lassen.

Hier die direkten Download-Hyperlinks (für Microsoft Windows):

Handbücher gibt’s auch:

Weil wir nett sind (und uns viel Verbreitung versprechen), ist die Software für Privatanwender, Institutionen wie Schulen und Universitäten, sowie für den Einsatz im Open-Source-Kontext komplett kostenlos erhältlich.

Freue mich über viel Feedback Eurerseits zum Programm!

(Vom Webdesign und -Layout haben wir uns übrigens von den Freunden bei TeamViewer und Mozilla inspirieren lassen. Dank geht auch an Brian von „Lost in Deutschland“ für die hervorragenden Übersetzungen ins Englische!)

Vorgehen bei Manifestdatei-Fehlern mit VC++-CRT-DLLs

Situation:

Eine C++-DLL mit managed und unmanaged Code läuft auf manchen Zielsystemen nicht, es erscheint eine Fehlermeldung:

Could not load file or assembly ‚ZetaHtmlTidy, Version=1.0.3236.15571, Culture=neutral, PublicKeyToken=null‘ or one of its dependencies. Diese Anwendung konnte nicht gestartet werden, weil die Anwenungskonfiguration nicht korrekt ist. Zur Problembehebung sollten Sie die Anwendung neu installieren. (Exception from HRESULT: 0x800736B1)

Dies, obwohl alle Dateien der CRT vorhanden sind:

  • Microsoft.VC90.CRT.manifest
  • msvcm90.dll
  • msvcp90.dll
  • msvcr90.dll

Ursache:

In meinem Fall war es so, dass die DLL nicht gegen die neueste Version der CRT gelinkt war. Sie war noch gegen die CRT aus Microsoft Visual Studio 2008 (SP 0) gelinkt, ausgeliefert habe ich aber die CRT-Version von Microsoft Visual Studio 2008, SP 1.

Konkret wurde die Version 9.0.30729.1 der CRT ausgeliefert, in der Manifest-Datei meiner Anwendung stand aber noch die Referenz auf 9.0.21022.8 drin.

Lösung:

Das Projekt neu kompilieren, aber mit der Besonderheit, dass der „/D“-Compiler-Schalter zusätzlich noch den Befehl „_BIND_TO_CURRENT_VCLIBS_VERSION=1“ mitgegeben bekam. Siehe Erklärung hier und hier.

Interessanterweise hatte ich erst Erfolg, als ich den Befehl vor alle anderen Befehle in den Projekt-Optionen schrieb. Also z.B. vor die „NDEBUG“-Präprozessor-Definition.

Außerdem habe ich zur Sicherheit den kompletten Ausgabeordner der zu kompilierenden DLL gelöscht.

Danach neu kompiliert und es hat funktioniert.

Anmerkungen:

Einige Hyperlinks zu guten Erklärungen bzw. Werkzeugen die Hilfreich waren:

  • Der bereits oben verlinkte Weblog-Artikel „Binding to the most recent Visual Studio Libraries“ beschreibt ausführlich was zu tun ist. Dort wird auch die Verwendung des Werkzeugs „dumpbin“ beschrieben, was mir hilfreich war.
  • Um zu sehen, auf welches CRT-Manifest/-Version tatsächlich in meiner erzeugten DLL verwiesen wird, ist das Werkzeug „Manifest View 1.0“ sehr hilfreich gewesen.
  • Ansonsten gibt’s zur Fehlersuche und allgemein zum Deployment von VC++-Anwendungen/-DLLs einiges in der MSDN-Bibliothek.

Ich hoffe der Artikel hier hilft anderen und mir zu einem späteren Zeitpunkt.

Einbruch in unser Bürogebäude

Mist! Gestern Nacht (04.10.2008 auf 05.10.2008), als ich tatsächlich mal zufälligerweise nicht im Büro war, wurde in unser Bürogebäude eingebrochen (schon wieder!)

Ein Lichtschacht, der falsch gesichert war, wurde aufgehebelt. Anschließend bei uns im 1. Stock an der Metall-Türe ziemlich herumgehebelt, aber zum Glück kam niemand durch.

Danach ging’s weiter in den 3. Stock zu einem Nachbarn und dort hat dann die Bürotür dran glauben müssen.

Ich habe einige Fotos gemacht und die gleich auch der Polizei mitgegeben (hübsche Polizistin!). Außerdem hier als Picasa-Album abgelegt.

Hoffentlich passiert so was ganz selten und am Besten überhaupt nie mehr!

Ergänzung:

Der nette Nachbar aus dem 3. OG rechts hat auf seiner Überwachungskamera zwar keine Personen gesehen, aber doch als das Licht an und aus ging. Die Zeiten waren:

04.10.2008, 21:12 Uhr an
04.10.2008, 21:30 Uhr aus
04.10.2008, 21:36 Uhr an
05.10.2008, 02:25 Uhr an
05.10.2008, 02:44 Uhr aus
05.10.2008, 02:47 Uhr an

Danke für die Daten!

Ergänzung:

Heute Morgen war noch die Kriminalpolizei da, uns ein Aktenzeichen nennen und Spuren aufnehmen. Der eine Polizist hat dann auch gleich Gummihandschuhe angezogen, aber ich hab dann gemeint, er solle sich doch jetzt erst mal um die Spurensicherung kümmern… Hehe, OK nur Spaß.

Naja, und nach 10 Minuten waren die dann wieder weg.