BOM size for different encodings

The number of bytes for the Byte Order Mark („BOM“) differs among the used encodings.

public static bool IsBomRelevantEncoding(
    Encoding encoding,
    out int bomSizeBytes )
{
    if ( encoding == Encoding.Unicode )
    {
        bomSizeBytes = 2;
        return true;
    }
    else if ( encoding == Encoding.UTF32 )
    {
        bomSizeBytes = 4;
        return true;
    }
    else if ( encoding == Encoding.UTF8 )
    {
        bomSizeBytes = 3;
        return true;
    }
    else if ( encoding == Encoding.UTF7 )
    {
        bomSizeBytes = 4;
        return true;
    }
    else
    {
        bomSizeBytes = 0;
        return false;
    }
}

References:

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! 🙂

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.

Fehlermeldung bei Verwendung eines UpdatePanel in einem WizardControl

Szenario

Ein ASP.NET-Ajax-UpdatePanel wird innerhalb eines ASP.NET-Wizard-Steuerelements verwendet. Genauer gesagt innerhalb eines WizardStep-Tags.

Fehlermeldung

Es trat beim Aufruf einer ASPX-Seite mit dieser Konstellation folgende Fehlermeldung auf:

Cannot unregister UpdatePanel with ID ‚myUpdatePanel‘ since it was not registered with the ScriptManager. This might occur if the UpdatePanel was removed from the control tree and later added again, which is not supported.

Parametername: updatePanel

Ursache

Wie ich nach langem Hin-und-her herausgefunden habe war folgender Code die Ursache (ja, Visual Basic.NET, shame on me, der Kunde will das so):

Protected Sub Page_Load(ByVal sender As Object, ByVal e As EventArgs)
MyWizard.DisplaySideBar = False
End Sub

Ich hatte also im Load-Ereignis der Seite, die das Wizard-Steuerelement enthalten hat die DisplaySideBar-Eigenschaft geändert.

Dies hat unmittelbar zu der oben beschriebenen Fehlermeldung geführt. Andere Eigenschaften, wie z.B. ActiveStepIndex, hatten nicht zu dem Fehler geführt und korrekt funktioniert.

Lösung

Eine Lösung die sich für mich als benutzbar erwiesen hat war, dass ich die DisplaySideBar-Eigenschaft deklarativ gesetzt habe. Also direkt im ASPX-Markup.

Das war aber für mich ungeeignet, da ich die Eigenschaft im Design-Modus auf True haben wollte und nur zur Laufzeit auf False.

Deshalb habe ich nach etwas längerem Ausprobieren herausgefunden, dass wenn ich die DisplaySideBar-Eigenschaft zu einem „früheren“ Zeitpunkt setze, diese korrekt funktioniert, ohne einen Fehler zu produzieren.

Und zwar im PreRender-Ereignis des Wizard-Steuerelements selbst:

Protected Sub MyWizard_PreRender(ByVal sender As Object, ByVal e As System.EventArgs)
    MyWizard.DisplaySideBar = False
End Sub

Damit hat alles wie gewünscht funktioniert.

Fehlermeldung „Der Name components wird bereits von einem anderen Objekt verwendet“ im Windows Forms Designer

Verhalten

Beim Öffnen des Windows Forms Designers kommt eine Fehlermeldung

„Der Name components wird bereits von einem anderen Objekt verwendet“

Ursache

Der Designer hat wohl was zerschossen. Genaue Ursache unbekannt.

Lösung

Auf dieser Seite schreibt Microsoft selbst:

So beheben Sie diesen Fehler:
Notieren Sie sich den genauen Wortlaut der Fehlermeldung, und wenden Sie sich an den Microsoft-Produktsupport.

Das ist natürlich Quatsch. In meinem Fall habe ich festgestellt dass in der Designer-Codedatei Folgendes drin stand:

private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
this.components = new System.ComponentModel.Container();

Also zwei Mal die selbe Zeile.

Nachdem ich eine davon gelöscht habe und das Projekt neu kompilierte, ging’s wieder.

Zugriff von .NET-Anwendungen auf Jet unter 64-bit-Windows-Betriebssystemen

Verhalten

Beim Ausführen von .NET-Anwendungen die via OleDB auf Microsoft-Access-Datenbanken (.mdb) zugreifen, kommt unter 64-Bit-Betriebssystemen folgende Fehlermeldung:

The ‚Microsoft.Jet.OLEDB.4.0‘ provider is not registered on the local machine.

Ursache

Der Microsoft-Jet-Treiber für den Zugriff auf Microsoft-Access-Datenbanken existiert nur in einer 32-Bit-Version, auch auf 64-Bit-Betriebssystemen.

Es gibt keine 64-Bit-Version und scheinbar ist auch keine geplant.

Lösungen

In diesem Forum-Thread habe ich sowohl die Lösung für Windows-Anwendungen, als auch für ASP.NET-Webanwendungen gefunden. Dort für Visual Basic beschrieben, aber auch für C# anwendbar.

Windows-Anwendungen (Konsole und Windows Forms)

Für Windows-Anwendungen im Visual Studio den CPU-Typ der Projektkonfiguration der betroffenen Projekte so einstellen, dass „x86“ als Zielsystem eingestellt ist, so wie in diesem Posting erklärt und hier detailliert beschrieben.

Zitat:

There is not a 64 bit version of jet that is why you get that error. To force your app to use the 32 bit change the target cpu to x86 in the advanced compiler options.

Dazu Rechtsklick auf das Projekt im Solution Explorer, dann dort Properties auswählen und dort Registerkarte Build, Auswahlliste Platform Target. Dort dann „x86“ auswählen, zuvor stand dort vermutlich „Any CPU“ drin.

In meinen Tests hat es ausgereicht, wenn ich bei einem Projekt, das über mehrere DLLs verteilt war, die Hauptanwendung (also das Projekt der EXE-Datei) als „x86“ gekennzeichnet habe und die DLLs auf „Any CPU“ gelassen habe.

ASP.NET-Webanwendungen

Für Webanwendungen muss der IIS, also der Webserver vom 64-Bit-Modus in den 32-Bit-Modus umgeschaltet werden. Dies gilt dann für alle Websites. In diesem Posting wird das erwähnt, in diesem Microsoft-Knowledge-Base-Artikel wird es beschrieben.

Zitat aus dem Posting:

If you want to run your ASP.NET app in 32-bit mode then you have run IIS in 32-bit mode. In addition, you cannot run IIS in 32 and 64-bit mode at the same time.

Zusammenfassung

Alles in allem ist Microsoft Jet also ausschließlich in 32-Bit verfügbar. Das ist recht traurig; der Grund ist sicherlich darin zu sehen, die Entwickler weg vom „alten“ Microsoft Access/Jet, hin zum SQL Server 2005 (Express) zu bewegen.

Ich find’s doof und lästig.