Generische Methode zur Überschreibung der ToString Methode eigener komplexer Klassen – C#

Im Zuge eines Projektes musste eine komplexe ineinander verschachtelte Klassenstrukturen aufgebaut werden, um eine ebenfalls komplexe Interfacestruktur einer externen Anwendung abzubilden.

Um bei Fehlermeldungen und beim Debuggen (ja ich oute mich, ich debugge auch Code) ordentliche Daten, anstelle des reinen Objektnamen über die ToString() Methode dargestellt zu bekommen, überschreibt man in der jeweiligen Klasse die ToString() Methode und gibt darin an, welche Daten ausgegeben werden sollen.

So in etwas sieht es im Debugger aus, wenn man ToString() nicht überschreibt:

image

Für, ich will es mal “Nicht so komplexe Klassen” nennen, kann man das auch im folgenden Stil machen.

        public override string ToString()
        {
            return "Ausschreibungsstatus= " + this.Ausschreibungsstatus + 
                " " + "Ausschreibungstext= " + this.Ausschreibungstext;
        }

Das ist schon besser und sieht in etwa so aussehen:

image

Schon besser, aber ….

Ich würde gerne ohne großen Schreibaufwand in jeder Klasse die Public Eigenschaften mit den enthaltenen Werten angezeigt bekommen.

Also musste ich mir hierfür etwas mehr generisches ausdenken.

Und wieder einmal lag die Lösung meines Problems in der Verwendung von Reflection.

Hier nun meine “generische” Methode zur Bildung einer für mich “sinnvollen” ToString() Ausgabe für meine komplexen Klassen.

public static string ClassPropertiesToString(object obj)
{
    string retVal = obj.GetType().ToString() + "\n";
    const BindingFlags Flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy;
    var sourceProperties = obj.GetType().GetProperties(Flags);

    foreach (PropertyInfo pi in sourceProperties)
    {
        if (pi.PropertyType.Namespace != "System")
            continue;

        retVal += string.Format("{0}:{1}\n", pi.Name, pi.GetValue(obj, null));
    }

    return retVal;
}

Ich denke der Code ist soweit Selbsterklärend, falls nicht, einfach einen Kommentar abgeben und fragen, dann erläutern wir die Details.

Hier noch der Vollständigkeit halber, das Ergebnis im Debugger:

image

Und bevor ich es vergesse.

So sieht dann die Überschreibung in der jeweiligen Klasse aus:

public override string ToString()
{
    return Lager.Global.Common.Generic.ClassPropertiesToString(this);
}

Generische Methode um gleichnamige Properties einer Klasse zu einem Objekt einer anderen Klasse zu kopieren – C#

Bei meiner Arbeit kommt es häufiger vor, dass ich umfangreiche Klassen (welche die Daten einer Datenbank repräsentieren) verwende die mal schnell über 50 oder 100 Public Properties (Datenbankfelder) enthalten.

imageNun kommt immer wieder vor, dass ich zum Beispiel zum Archivieren solcher Daten, damit meine ich die Daten aus einer Tabelle in eine andere zu verschieben, nicht auf SQL Skripte auf dem Server zugreifen kann oder möchte, da dieses verschieben noch mit zusätzlicher Businesslogik hinterlegt werden muss.

Was bleibt ist dann so etwas in dieser Art:

var auftrag = new Auftrag
    { Position = 1, Artikel = "Mars", Beschreibung = "Schoko Riegel", Preis = 1.50 };

var auftragsArchiv = new AuftragArchiv();
auftragsArchiv.Artikel = auftrag.Artikel;
auftragsArchiv.Beschreibung = auftrag.Beschreibung;
auftragsArchiv.Preis = auftrag.Preis;

if (auftragsArchiv.Save())
{
    auftrag.Delete();
}

Diese Vorgehensweise kann bei umfangreichen Klassen sehr arbeitsintensiv sein.

Nun gibt es verschiedene Ansätze um sich diesem Problem anzunehmen. Nachfolgend möchte ich einige Lösungsansätze unter Verwendung von Reflection aufzeigen.

Ich nehme an, jeder kennt die Methode ToString().

Die Idee ist nun eine generische To…. Methode zu erstellen, welche die Aufgaben wie im obigen Beispiel dargestellt, universell erledigt.

Das Ziel soll sein, dass die Public Property Werte eines Objektes, durch Aufruf einer Methode, alle Werte die den gleichen Property Namen  haben in ein anderes Objekt einer anderen Klasse kopiert werden.

Hier nun die Variante 1 (Methode einer Klasse):

public T ToTobject<T>(T target)
{
    const BindingFlags Flags = BindingFlags.Instance | BindingFlags.Public;
    var sourceProperties = this.GetType().GetProperties(Flags);

    foreach (PropertyInfo pi in sourceProperties)
        {
            if (pi.CanWrite)
            {
				var propInfoObj = target.GetType().GetProperty(pi.Name);
				if (propInfoObj != null)
				{
					var propValue = pi.GetValue(this, null);
					propInfoObj.SetValue(target, propValue, null);
				}                             
            }
        }

    return target;
}

Der Aufruf um diese Variante zu verwenden sieht dann so aus:

var auftrag = new Auftrag
	{ Position = 1, Artikel = "Mars", Beschreibung = "Schoko Riegel", Preis = 1.50 };

var auftragsArchiv = new AuftragArchiv();

auftragsArchiv = auftrag.ToTobject(auftragsArchiv);

if (auftragsArchiv.Save())
{
	auftrag.Delete();
}

Sieht doch schon ganz gut aus.

Hier noch eine Variante, die noch etwas mehr generisch arbeitet und nur noch mitgeteilt bekommt, welchen Type ich als Rückgabe der Methode erwarte, die Instanz des Objekts der neuen Klasse wird in der Methode selbst erzeugt.

public T ToTobject<T>()
{
	var constructorInfo = typeof(T).GetConstructor(new Type[] { });
	if (constructorInfo != null)
	{
		var target = (T)constructorInfo.Invoke(new object[] { });

		const BindingFlags Flags = BindingFlags.Instance | BindingFlags.Public;
		var sourceProperties = this.GetType().GetProperties(Flags);

		foreach (PropertyInfo pi in sourceProperties)
		{
			if (pi.CanWrite)
			{
				if (!pi.IsDefined(typeof(XmlIgnoreAttribute), false))
				{
					var propInfoObj = target.GetType().GetProperty(pi.Name);
					if (propInfoObj != null)
					{
						var propValue = pi.GetValue(this, null);
						propInfoObj.SetValue(target, propValue, null);
					}
				}
			}
		}

		return target;
	}

	return default(T);
}

Diese Variante wird dann so verwendet:

var auftrag = new Auftrag
	{ Position = 1, Artikel = "Mars", Beschreibung = "Schoko Riegel", Preis = 1.50 };

var auftragsArchiv = auftrag.ToTobject<AuftragArchiv>();

if (auftragsArchiv.Save())
{
	auftrag.Delete();
}

Diese Variante gefällt mir schon etwas besser

Aber das ist noch nicht das Ende der Methoden Evolution!

Es geht noch generischer (und statisch für alle Klassen verwendbar) und diese Variante folgt nun.

public static T ToTobject<T>(object source)
{
	var constructorInfo = typeof(T).GetConstructor(new Type[] { });
	if (constructorInfo != null)
	{
		var target = (T)constructorInfo.Invoke(new object[] { });

		const BindingFlags Flags = BindingFlags.Instance | BindingFlags.Public;
		var sourceProperties = source.GetType().GetProperties(Flags);

		foreach (PropertyInfo pi in sourceProperties)
		{
			if (pi.CanWrite)
			{
				var propInfoObj = target.GetType().GetProperty(pi.Name);
				if (propInfoObj != null)
				{
					var propValue = pi.GetValue(source, null);
					propInfoObj.SetValue(target, propValue, null);
				}
			}
		}

		return target;
	}

	return default(T);
}

Diese nun statische Variante kann auf jedes beliebige Objekt jeder beliebigen Klasse angewendet und für die Rückgabe jeder beliebigen Klasse verwendet werden. Natürlich nur solange diese Klassen auch gleiche Properties enthalten.

Und so wird diese statische Methode verwendet:

var auftrag = new Auftrag
	{ Position = 1, Artikel = "Mars", Beschreibung = "Schoko Riegel", Preis = 1.50 };

var auftragsArchiv = Auftrag.ToTobject<AuftragArchiv>(auftrag);

if (auftragsArchiv.Save())
{
	auftrag.Delete();
}

Mir hat es Spaß gemacht diese Methodik zu entwickeln, evtl. hilft es auch mal jemand anderem beim Lösen “seines” Problem.

Anregungen und Kritik wie immer gerne als Kommentar zum Beitrag.

ASCII Schnittstellen mit Hilfe von Custom Attributes komfortabel erstellen – C#

Viele kennen dass Problem, dass Daten zwischen verschiedenen Welten auch Heute noch immer durch Übergabe von Schnittstellendateien, seien es EDI oder auch ASCII Dateien, die einen festen Satzaufbau haben, übergeben werden.

Die Definitionen sehen oft wie folgt aus:

Wobei die nähere Definition etwas komplexer sein kann:

Art: A alphanumerisches Feld N numerisches Feld Länge L Gesamtlänge des Datenfeldes Nachkomma K davon Nachkommastellen Position von von Stelle ... im Datensatz bis bis Stelle ... im Datensatz M/O M Muss-Feld O Kann-Feld (optional) M/O abhängig von anderen Angaben handelt es sich wahlweise um ein Muss- oder ein Kann-Feld

Der Output, also die Übertragungsdatei enthält dann Daten die ähnlich der nachfolgenden Abbildung aussehen können.

image

Eine durchaus übliche Vorgehensweise solche Daten erstellen zu können ist es, sich Klassen zu erstellen, die dem eigentlichen benötigten Satzaufbau entsprechen, diese in einer Füll Routine mit Daten füllt und anschließend die Daten der Klasseneigenschaften mit string.Format Stück für Stück ausgibt.

Wäre es nicht wünschenswert, bereits bei der Definition der Klasse festlegen zu können, welche Ausgabeeigenschaften (Ausgabeformat wie führende Nullen, Links oder Rechtsbündig, Anzahl Nachkommastellen usw.) die jeweilige Eigenschaft besitzt und an welcher Position die Eigenschaft ausgegeben werden soll, anzugeben, damit man anschließend die Daten einfach mit einer einzelnen Methode im richtigen Format ausgeben kann.

Und genau da setze ich mit meiner Lösung an:

Man kann mit sogenannten Custom Attributes beliebig zusätzliche Informationen an jedes beliebige Klassenelement anheften. Hierzu muss man eine Klasse erstellen die von System.Attributes abgeleitet wird.

Siehe Nachfolgendes Beispiel, welches ziemlich selbsterklärend sein sollte.

using System;
    using System.Reflection;

    public enum ExportFieldType
    {
        /// <summary>
        /// Alphanumeric Field
        /// </summary>
        Alpha,

        /// <summary>
        /// Numeric Field
        /// </summary>
        Numeric
    }

    [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
    public class ExportFieldAttribute : Attribute
    {
        private readonly int index;

        private readonly int length;

        private readonly ExportFieldType fieldType;

        private readonly int precision;

        private readonly bool optionalField;

        private readonly int fromPos;

        private readonly int toPos;

        /// <summary>
        /// Initializes a new instance of the <see cref="ExportFieldAttribute"/> class.
        /// </summary>
        /// <param name="index">The index.</param>
        /// <param name="fieldType">Type of the field.</param>
        /// <param name="length">The length.</param>
        /// <param name="precision">The precision.</param>
        /// <param name="optionalField">if set to <c>true</c> [optional field].</param>
        /// <param name="fromPos">From pos.</param>
        /// <param name="toPos">To pos.</param>
        public ExportFieldAttribute(int index, ExportFieldType fieldType, int length, int precision, bool optionalField = false, int fromPos = -1, int toPos = -1)
        {
            this.index = index;
            this.fieldType = fieldType;
            this.length = length;
            this.precision = precision;
            this.optionalField = optionalField;
            this.fromPos = fromPos;
            this.toPos = toPos;
        }

        public int Index
        {
            get
            {
                return this.index;
            }
        }

        public ExportFieldType FieldType
        {
            get
            {
                return this.fieldType;
            }
        }

        public int Length
        {
            get
            {
                return this.length;
            }
        }

        public int Precision
        {
            get
            {
                return this.precision;
            }
        }

        public bool OptionalField
        {
            get
            {
                return this.optionalField;
            }
        }

        public int FromPos
        {
            get
            {
                return this.fromPos;
            }
        }

        public int ToPos
        {
            get
            {
                return this.toPos;
            }
        }
    }

Nun kann ich in der Klassendefinition in der ich die Datensatzstruktur der Exportdatei abbilde, dieses Attribut zusätzlich zu den Eigenschaften der Klasse verwenden um die für den korrekten Export benötigen Informationen anzuheften.

Die Klasse könnte so aussehen:

    public class Ksta
    {
        [ExportField(0, ExportFieldType.Alpha, 5, 0)]
        public string Satza
        {
            get
            {
                return "KSTA_";
            }
        }

        [ExportField(1, ExportFieldType.Numeric, 2, 0)]
        public int Gsber
        {
            get
            {
                return 1;
            }
        }

        [ExportField(2, ExportFieldType.Numeric, 8, 0)]
        public int Kvkda { get; set; }

        [ExportField(3, ExportFieldType.Numeric, 8, 0)]
        public int Dtkda { get; set; }

        [ExportField(4, ExportFieldType.Numeric, 5, 0)]
        public int Kvkvn { get; set; }

        [ExportField(5, ExportFieldType.Numeric, 5, 0)]
        public int Ssbpa { get; set; }

        [ExportField(6, ExportFieldType.Numeric, 8, 3)]
        public int Sskda { get; set; }
    }

Wie aber können wir diese zusätzlichen Attribute verwenden?

Ich möchte dies an einem einfachen Beispiel demonstrieren. Hierzu erweitere ich die Klasse ExportFieldAttribute um folgende Methode:

public static string ExportFieldToString(object obj, bool sorted = true)
{
	string buffer = string.Empty;
	PropertyInfo[] pi = obj.GetType().GetProperties();
	if (sorted)
	{
		var propertyInfoSorted = new PropertyInfo[pi.Length];
		foreach (var propertyInfo in pi)
		{
			var attribs = (ExportFieldAttribute[])propertyInfo.GetCustomAttributes(typeof(ExportFieldAttribute), true);
			if (attribs.Length > 0)
			{
				propertyInfoSorted[attribs[0].Index] = propertyInfo;
			}
		}

		pi = propertyInfoSorted;
	}

	foreach (var propertyInfo in pi)
	{
		if (propertyInfo == null)
		{
			continue;
		}

		var attribs = (ExportFieldAttribute[])propertyInfo.GetCustomAttributes(typeof(ExportFieldAttribute), true);

		if (attribs.Length > 0)
		{
			ExportFieldAttribute attrib = attribs[0];
			var o = propertyInfo.GetValue(obj, null);
			if (o == null)
			{
				o = string.Empty;
			}

			if (attrib.FieldType == ExportFieldType.Alpha)
			{
				string t = string.Format(o.ToString().PadRight(attrib.Length));
				buffer += t;
			}

			if (attrib.FieldType == ExportFieldType.Numeric)
			{
				if (attrib.Precision > 0)
				{
					string concat;
					var buf = o.ToString().Split(',');
					if (buf.Length > 1)
					{
						buf[1] = buf[1].PadRight(attrib.Precision, '0').Substring(0, attrib.Precision);
						concat = buf[0] + buf[1];
					}
					else
					{
						concat = buf[0] + string.Empty.PadRight(attrib.Precision, '0');
					}
					string t = string.Format(concat.PadLeft(attrib.Length, '0'));
					buffer += t;
				}
				else
				{
					string t = string.Format(o.ToString().PadLeft(attrib.Length, '0'));
					buffer += t;
				}
			}
		}
	}
	return buffer;
}

Ich möchte an dieser Stelle nun auch keine aufwändige Erklärung der Methode vornehmen, ich denke dass derjenige der sich mit diesem Problem beschäftigt erkennen wird was darin geschieht.

Und um diese Methode zu verwenden, also um den Satzaufbau der Daten so zu erhalten wie ich ihn mit den Attributen definiert habe, überschreibe ich einfach die ToString() Methode der Klasse in welcher ich die Datenstruktur abgebildet habe.

Das sieht dann so aus (In der Klasse Ksta):

        public override string ToString()
        {
            return ExportFieldAttribute.ExportFieldToString(this);
        }

Wenn ich nun eine Instanz der Klasse Ksta erstelle, diese dann mit Daten fülle und anschließen die ToString Methode aufrufe erhalten ich die Daten genau in der definierten Struktur.

Wenn jemand noch Fragen zu dem Thema hat, oder doch noch weiterführende Erläuterungen benötigt, dann einfach per Kommentar die Fragen oder Anregungen stellen.

Alles WiX oder was – Windows Installer XML toolset für Dummies

Im Zuge der Zukunftssicherung (Heute nennt man das Refactoring) eines meiner älteren aber immer noch aktiven Projekte, einem Windows Forms Projekt sollte es dieses mal nicht nur dem eigentlichen Code sondern dem gesamten Build und Deployment Prozess an den Kragen gehen.

imageMit den, bis zur Visual Studio 2010 enthaltenen, und Stand Heute mit der nächsten Visual Studio Version nicht mehr unterstützen, “Visual Studio Setup Projekte”, war ich eigentlich noch nie wirklich zufrieden. Also muss etwas neues her, mit dem ich dieses ungeliebte Setup Projekt ersetzen kann und mein Projekt auch noch mit der nächsten Visual Studio Version weiter entwickeln und distribuieren kann.

Nach ein wenig Recherche und ein wenig Gezwitscher auf Twitter stand fest, dass WiX wohl die beste Lösung sei um das Deployment der Anwendung durchzuführen.

WiX ist ein von Microsoft initiiertes Open Source Projekt dass unter anderem von Microsoft selbst für die  Distribution von Microsoft Office verwendet wird.

Nachdem klar war, das WiX in Zukunft bei mir für die Installation meines Projektes zuständig sein soll, habe ich über Twitter von Sebastian Seidel einen Link auf einen Beitrag erhalten in welchem beschrieben wird wie man in 5 einfachen Schritten aus einem Visual Studio Setup ein WiX Setup Projekt machen kann.

Ich bin dann dieser Anleitung gefolgt und habe die Konvertierung und Änderungen laut dem oben genannten Beitrag durchgeführt.

Wobei ich sagen muss, dass diese Konvertierung zwar ganz schön ist, aber leider nicht so ohne weiteres direkt zum Erfolg führt.

imageIch habe dann mit ein wenig Hilfe von Sebastian, Danke noch mal dafür, relativ schnell, erst einmal ein generell funktionierendes Setup mit WiX zum laufen bekommen. Leider noch ohne wirklich zu wissen wie WiX tatsächlich funktioniert und wie umfangreich WiX in Wirklichkeit ist.

Da ich aber nicht so leicht zufrieden zu stellen bin und mir das WiX Setup,  welches aus dem decompilierten alten Setup erstellt wurde, nicht gefallen hat (da waren zu viele statische Angaben enthalten und es hatte keine echte nachvollziehbare Struktur) habe ich mich mit WiX näher beschäftigt.

Nachdem ich ein paar Tage mit WiX gearbeitet habe, würde ich sagen, dass es sich nicht lohnt ein Setup Projekt zu decompilieren um dieses dann manuell nachzuarbeiten.

Meiner Meinung nach ist es einfacher ein Basistemplate mit den wichtigsten Funktionalitäten, gepaart mit ein wenig Grundwissen über WiX zu nehmen und sich das benötigte Setup Projekt manuell zu erstellen.

Und genau ein solches Basistemplate möchte ich hier zusammen mit dem notwendigen Basiswissen zu WiX vorstellen und vermitteln.

Wer WiX noch nicht auf seinem Rechner installiert hat, sollte das nun aber spätestens nachholen.

Hier der Link zum Download von WiX

Nachdem man WiX installiert hat und Visual Studio startet, findet man dort neue Projektvorlagen:

SNAGHTMLb687d8

In diesem Beitrag dreht sich alles nur um das eigentliche Setup Projekt die anderen Projekt Vorlagen sind dann eher für die Fortgeschrittenen.

Um die Funktionsweise von WiX direkt selbst ausprobieren zu können, habe ich ein Testprojekt erstellt dass sowohl eine Windows Forms Anwendung, wie eine Class Library und das zugehörige Wix Setup Projekt in einer Solution enthält.

Also einfach die Solution (VS2010) herunterladen, entpacken, BS starten und loslegen.

Der Einfachheit halber habe ich alle Erklärungen direkt als Kommentar in das WiX Skript aufgenommen.

So hier das WiX Script zum anschauen. Im Anschluss folgt dann auch noch der Link zum herunterladen der gesamten Test Solution.

<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
  <!--  <?define Manufactor = "Hans-Peter Schelian"?>
        Anstelle den Manufactor an mehreren Stellen anzugeben weise ich hier lieber einer Variablen den Wert zu
        und verwende im weiteren Verlauf einfach die Variable
  -->
  <!--  <?define UpgradeGuid = "{3B563A68-99BC-4528-9356-295ADB67909A}"?>
        Ganz Wichtig, die Guid für den UpgradeCode darf nach der ersten Installation nicht mehr geändert werden
        aus diesem Grund weise ich diese Guid einer Variablen zu die ich dann im weiteren Verlauf im Bereich
        <Product UpgradeCode="$(var.UpgradeGuid)" /> und  im Bereich <Upgrade Id="$(var.UpgradeGuid)" anstelle
        der Eingabe der eigentlichen Guid verwenden kann.
  -->

  <!-- Hier nun alle Defines die durch den Präprozessor aufgelöst werden -->
  <?define Manufactor = "Hans-Peter Schelian"?>
  <?define UpgradeGuid = "{3B563A68-99BC-4528-9356-295ADB67909A}"?>

  <!--  Erklärungen zum Bereich <Product> Siehe auch http://wix.sourceforge.net/manual-wix3/wix_xsd_product.htm

        Id="*" Mit dem + wird erreicht, dass bei jedem erzeugen des Setup eine neue Guid erstellt wird.
          Man könnte natürlich auch alternativ eine feste Guid vergeben, dass würde dann so ausssehen:
          Id="{2BE1FDE8-FAEE-48B5-AA08-FD2FE26D7FB2}"

        Name="WiXDemoProjekt" Name des Projektes wie es in "Programme und Funktionen" angezeigt wird

        Version="!(bind.FileVersion.MyApplicationFile)" - Das ist die Programmversion des Produktes,
          entweder man muss die Version manuell angeben: Version="1.0.1.1" oder man kann wie ich es hier
          demonstriere, Binder Variablen des WiX Linker verwenden um Dynamisch die Vesionsnummer aus
          einer beliebiegen Datei des Projektes zu ermitteln.
          Im Beispiel verwende ich die Möglichkeit die Version welche mit [assembly: AssemblyFileVersion("1.0.0.0")]
          in der AssemblyInfo gesetzt wurde aus der Datei auszulesen.

        Manufacturer="$(var.Manufactor)" Zuweisung des in der Präprozessor Variablen gespeicherten Herstellers,
          alternativ kann man natürlich auch eine manuelle Zuweiung Manufacturer="Hans-Peter Schelian" vornehmen

        UpgradeCode="$(var.UpgradeGuid)"> Zuweisung der in der Präprozessor Variablen gespeicherten Guid,
          alternativ kann man die Zuweisung UpgradeCode="{2BE1FDE8-FAEE-48B5-AA08-FD2FE26D7FB2}"> auch manuell
          durchführen.
  -->
  <Product
  Id="*"
  Name="WiXDemoProjekt"
  Language="1033"
  Version="!(bind.FileVersion.MyApplicationFile)"
  Manufacturer="$(var.Manufactor)"
  UpgradeCode="$(var.UpgradeGuid)">

    <!-- Erklärungen zum Bereich <Package> http://wix.sourceforge.net/manual-wix3/wix_xsd_package.htm
      InstallerVersion="200" Ab Windows Installer 2.0
      Compressed="yes" - So werden die Dateien im MSI File komprimiert
      Description="Installiert das WiXDemoProjekt" - Eine Beschreibung für das Installer Package
      Manufacturer="Hans-Peter Schelian" - Und wieder der Hersteller siehe auch <Procuct Manufacturer="" />
      Comments="Ich wünsche allen viel Spaß mit diesem Beispiel" - Das was es bedeutet ein Kommentar
    -->
    <Package
    InstallerVersion="200"
    Compressed="yes"
    Description="Installiert das WiXDemoProjekt"
    Manufacturer="$(var.Manufactor)"
    Comments="Ich wünsche allen viel Spaß mit diesem Beispiel"
  />

    <!-- Erklärungen zum Bereich <Media> Siehe auch http://wix.sourceforge.net/manual-wix3/wix_xsd_media.htm
    -->
    <Media
    Id="1"
    Cabinet="media1.cab"
    EmbedCab="yes"
  />

    <!-- ARP Support Oder wie konfiguriert man Add/Remove Programs mit dem Windows Installer
        Siehe auch http://msdn.microsoft.com/en-us/library/aa368032.aspx
    -->
    <Property Id="ARPHELPTELEPHONE" Value="XXXXX/XXXXXXX" />
    <Property Id="ARPHELPLINK" Value="https://blog.schelian.de" />
    <Property Id="ARPCONTACT" Value="Schelian IT Beratung" />
    <Property Id="ARPCOMMENTS" Value="Alles WiX oder was" />
    <Property Id="ARPURLINFOABOUT" Value="http://www.schelian.de" />

    <!-- Erklärungen zum Bereich <Upgrade>
        Upgrade Id="$(var.UpgradeGuid)"> hier Zuweisung durch Präprozessor Variable, siehe auch Hinweise
          zu define UpgradeGuid

        <UpgradeVersion Minimum="!(bind.FileVersion.MyApplicationFile)" - Hier wird festgestellt ob
          bereits ein neueres Produkt (Neuere Version) installiert ist.

        <UpgradeVersion Minimum="0.1.0.0" - Prüfung ob bereits eine frühere Version installiert ist

        <UpgradeVersion Minimum="1.0.0.0" Maximum="99.0.0.0" - Ist das Prpdukt in welcher Versio auch immer
            bereits auf dem Rechner installiert, dann setzt die internet Variable PREVIOUSVERSIONSINSTALLED,
            dadurch wird verhindert, dass selbst dann wenn man eine Version die nicht als Upgrade gilt
            (nur im vierten Bereich) der Versionsnummer geändert ist, nicht dazu führt, dass es zu doppelten
            Einträgen im Add/Remove Programms kommt.
    -->
    <Upgrade Id="$(var.UpgradeGuid)">
      <UpgradeVersion Minimum="!(bind.FileVersion.MyApplicationFile)"
                      IncludeMinimum="no"
                      OnlyDetect="yes"
                      Language="1033"
                      Property="NEWPRODUCTFOUND" />
      <UpgradeVersion Minimum="0.1.0.0"
                      IncludeMinimum="yes"
                      Maximum="!(bind.FileVersion.MyApplicationFile)"
                      IncludeMaximum="no"
                      Language="1033"
                      Property="UPGRADEFOUND" />
      <UpgradeVersion Minimum="1.0.0.0" Maximum="99.0.0.0"
         Property="PREVIOUSVERSIONSINSTALLED"
         IncludeMinimum="yes" IncludeMaximum="no" />
    </Upgrade>

    <!-- So nun wird die Verzeichnis Struktur für die Installation aufgebaut
      <Directory Id="TARGETDIR" Name="SourceDir"> Diese Zeile setzt das Root Verzeichnis der Installation
        "\"
      <Directory Id="ProgramFilesFolder"> verwendet eine durch den Windows Installer vordefinierte Id zur
        Verwendung der Standard Programmpfads.
        "C:\Program Files"
      <Directory Id="CompanyFolder" Name="Schelian IT Beratung">
        "C:\Program Files\Schelian IT Beratung\"

      <Directory Id="INSTALLLOCATION" Name="WiXDemoProjekt">
        "C:\Program Files\Schelian IT Beratung\WiXDemoProjekt\"
    -->
    <Directory Id="TARGETDIR" Name="SourceDir">
      <Directory Id="ProgramFilesFolder">
        <Directory Id="CompanyFolder" Name="Schelian IT Beratung">
          <Directory Id="INSTALLLOCATION" Name="WiXDemoProjekt">
            <!-- Und dahinein packen wir eine Component, in diesem Fall Unser Ausführbares Programm
                Jede Component muss eine eindeutige Id bestzen, diese wird weiter unter verwendet um die Component
                einem Feature zuzuordnen.
                Eine Component sollte eine Guid besitzten, damit es später Möglich ist anhand dieser Guid die Datei
                eindeutig zu identifizieren, damit man mit dem MS Installer, eine Reparatur einer vorhandenen Installation
                durchführen kann.
            -->
            <Component Id="ProductComponent" Guid="{4A5619DF-5841-48EC-8D7C-D368718C6D1A}">
              <!-- Innerhalb der Komponente bestimmen wir dann die zu installierende Datei
                  Jedes File muss einen eindeutige Id besitzen, der Name ist der Dateiname am Zielort und
                  mit Source wird die Quelle der Datei angegeben.

                  Name="$(var.WixDemoProjekt.TargetFileName)" nutzt die Möglichkeit den Zielnamen aus dem
                  Visual Studio Projekt zu übernehmen, dass hat den Vorteil, dass man sich keine Gedanken
                  darüber machen muss das Setup anfassen zu müssen, wennm man den Ausgabe Namen im VS Projekt
                  ändert.

                  Source="$(var.WixDemoProjekt.TargetPath)" nutzt ebenfalls die Möglicheit der Visual Studio
                  Project References und Variablen.
                  Siehe auch http://wix.sourceforge.net/manual-wix3/votive_project_references.htm
                  Man miuss übrigens um diese References nutzen zu können im WiX Setup Projekt eine Reference auf das
                  Visual Studio Projekt setzen.

                  KeyPath="yes" Für nähere Informatinonen siehe http://wix.sourceforge.net/manual-wix3/wix_xsd_file.htm
              -->
              <File Id="MyApplicationFile" Name="$(var.WixDemoProjekt.TargetFileName)" Source="$(var.WixDemoProjekt.TargetPath)" KeyPath="yes">
                <!-- Dann legen wir doch gleich noch einen Desktop Shortcut für das Projekt an. -->
                <Shortcut Advertise="yes" Id="DesktopShortCut" Name="WiXDemoProjekt" Directory="DesktopFolder" WorkingDirectory="INSTALLLOCATION" Description="WiXDemoProjekt Programm ausführen" Icon="Icon.exe">
                  <!-- Und dieses Icon soll für den Shortcut verwendet werden -->
                  <Icon Id="Icon.exe" SourceFile="$(var.WixDemoProjekt.TargetPath)" />
                </Shortcut>
              </File>
            </Component>
            <!-- Nun muss für jedes weitere File dass installiert werden soll, eine Component erstellt werden
                Es wäre auch möglich mehrere Files in einer Component zusammenzufassen, allerdings würde ich das
                nur empfehlen, wenn man tgatsächlich einzeln zu installierende Feature implementiert, die je
                Component aus mehereren Dateien bestehen.
            -->
            <Component Id="WiXDemoLib" Guid="{777028E8-DA56-400E-9C71-844AE328E8BF}">
              <!-- Hier nun die zu installierende Datei der Component hinzufügen -->
              <File Id="WiXDemoLib" Name="WiXDemoLib.dll" Source="$(var.WixDemoProjekt.TargetDir)WiXDemoLib.dll" KeyPath="yes" />
            </Component>
            <!-- Beispiel wie man eine App.Config in das Setup aufnehmen und während der Setup Erstellung umbenennen kann
                Hier wird aus der app.config die WixDemoProjekt.Exe.Config
            -->
            <Component Id="app.config" Guid="{EE4CA09D-2C10-48E4-946A-D8B9242F557E}">
              <File Id="app.config" Name="WixDemoProjekt.Exe.Config" Source="$(var.WixDemoProjekt.ProjectDir)app.config" KeyPath="yes" />
            </Component>
            <!-- Und hier ein Beispiel wie man Datei in Unterverzeichnis am Zielort kopieren kann-->
            <Directory Id="IMAGEFOLDER" Name="Images">
              <Component Id="WiX.image" Guid="{ACD0322F-8170-4C67-84BC-161FCEE6274A}">
                <File Id="WiX.image" Name="wixlogo.png" Source="$(var.WixDemoProjekt.TargetDir)images\wixlogo.png" KeyPath="yes" />
              </Component>
            </Directory>
          </Directory>
        </Directory>
      </Directory>

      <!-- Verknüpfungen im Startmenü setzen
          Siehe auch http://wix.sourceforge.net/manual-wix3/create_start_menu_shortcut.htm
      -->
      <Directory Id="ProgramMenuFolder">
        <Directory Id="MyShortCutsDir" Name="WiXDemoProjekt">
          <Component Id="ShortCutComponent" Guid="{B51BC276-F509-4F25-9738-EE176445D073}">
            <Shortcut Id="ProgShortCut" Name="WiXDemoProjekt" Description="WiXDemoProjekt Programm Verkmüpfung" Target="[INSTALLLOCATION]WixDemoProjekt.Exe"></Shortcut>
            <Shortcut Id="UninstallShortCut" Name="Uninstall WiXDemoProjekt" Target="[System64Folder]msiexec.exe" Arguments="/x [ProductCode]"/>
            <RemoveFolder Id="RemoveMyShortCuts" On="uninstall"/>
            <RegistryValue Root="HKCU" Key="Software\Schelian IT Beratung\WiXDemoProjekt" Name="installed" Type="integer" Value="1" KeyPath="yes"/>
          </Component>
        </Directory>
      </Directory>
      <!-- Wird benötigt um den Desktop Shortcut anlegen zu können -->
      <Directory Id="DesktopFolder" Name="Desktop" />
    </Directory>

    <!-- Feature Bereich hier als Basistemplate nur ein Feature (Alles installieren)
        Siehe auch http://wix.sourceforge.net/manual-wix3/wix_xsd_feature.htm
    -->
    <Feature Id="ProductFeature" Title="WiXDemoProjekt" Level="1">
      <ComponentRef Id="ProductComponent" />
      <ComponentRef Id="WiXDemoLib" />
      <ComponentRef Id="app.config" />
      <ComponentRef Id="WiX.image" />
      <ComponentRef Id="ShortCutComponent" />
    </Feature>

    <!-- Das was nun folgt ist schon ein wenig Finetuning CustomActions
        siehe auch http://wix.sourceforge.net/manual-wix3/wix_xsd_customaction.htm
    -->
    <!-- Keinen Downgrade zulassen -->
    <CustomAction Id="PreventDowngrading"
                  Error="Es ist bereits eine neuere Version instlliert." />

    <!-- Nun noch die auszuführende Reihenfolg angeben
        siehe auch http://wix.sourceforge.net/manual-wix3/wix_xsd_installexecutesequence.htm
    -->
    <InstallExecuteSequence>
      <Custom Action="PreventDowngrading"
              After="FindRelatedProducts">NEWPRODUCTFOUND</Custom>
      <RemoveExistingProducts After="InstallFinalize" />
    </InstallExecuteSequence>

    <!-- Wenn ein neueres Produkt gerunden wurde, soll das auch in der UI ausgegeben werden
        siehe auch http://wix.sourceforge.net/manual-wix3/wix_xsd_installuisequence.htm
    -->
    <InstallUISequence>
      <Custom Action="PreventDowngrading"
              After="FindRelatedProducts">NEWPRODUCTFOUND</Custom>
    </InstallUISequence>

    <!-- So kann man eine eigene Lizenzmeldung ausgeben -->
    <WixVariable Id='WixUILicenseRtf' Overridable='yes' Value='$(var.WixDemoProjekt.ProjectDir)Lizenzbestimmungen.rtf'/>

    <!-- UI Definition für Minimales Setup
    Um diese UI Definition vornehmen zu können,
    muss im WiX Projekt die WixUIExtension.dll als Reference hinzugefügt werden
    -->
    <UIRef Id="WixUI_Minimal" />
    <UIRef Id="WixUI_ErrorProgressText" />
  </Product>
</Wix>

WixDemoProjekt (VS2010 Solution)

BlogEngine.NET – VS2010 – Master Page error beim öffnen der default.aspx

Beim Versuch die Datei default.aspx im Design Mode aus Visual Studio 2010 heraus zu öffnen wurde die folgendende Fehlermeldung ausgegeben:

Dieses ist nicht unbedingt ein BlogEngine spezifisches Problem und kann auch mit anderen Projekten auftreten.

Das Problem tritt auf, da in der verwendeten Basisklasse „BlogBasePage“ die Methode  OnPreInit vom PreInit Event aufgerufen wird. In dieser Methode wird eine Masterpage benötigt, da diese dort an die Basisklasse.MasterPageFile zugewiesen wird.

Siehe nachfolgenden Code Auszug:

this.MasterPageFile = string.Format("{0}themes/{1}/site.master", Utils.RelativeWebRoot, this.theme);

Nun gibt es die Möglichkeit direkt in der web.config eine default masterpage anzugeben, und genau dies ist auch die Lösung unseres Problems:

Der Eintrag muss wird in der Region „pages“ der web.config vorgenommen.
Dieser Bereich der web.config sieht vor der Änderung wie folgt aus:

<pages
	enableSessionState="false"
	enableViewStateMac="true"
	enableEventValidation="true"
>

Nun gibt es die Möglichkeit über den Parameter masterPageFile in der web.config eine default Master Page anzugeben, und genau dies machen wir, indem wir den Eintrag in der web.config, wie folgt ändern:

<pages
	enableSessionState="false"
	enableViewStateMac="true"
	enableEventValidation="true"
	masterPageFile="~/themes/Standard/site.master"
>

Man beachte den neuen Parameter

masterPageFile=“~/themes/Standard/site.master“

Dieser setzt die Master Page aus dem Standard Theme als Default Master Page, welche dann in der Basisklasse BlogBasePage verwendet werden kann, so dass der hier beschriebene Fehler nicht mehr auftritt.

Man kann natürlich auch jede andere Master Page aus jedem beliebigen Theme verwenden. Am besten verwendet man die Master Page aus dem verwendeten Theme.

Und nachdem diese Einstellung in der web.config vorgenommen wurde, kann man die default.aspx ohne den hier beschriebenen Fehler öffnen.

DotNetNuke – FolderController – Obsolete Klasse

Im vorläufig letzten Teil meiner kleinen Serie „Clean Code meiner DotNetNuke Module“ geht es Heute um eine ganze Klasse, welche unter DotNetNuke 6.0 nicht mehr weiter verwendet werden soll.

Die Klasse wird mit Obsolete gekennzeichnet und soll durch die neue Klasse FolderManager ersetzt werden.

Allerdings trifft dies nicht für alle Methoden aus der alten Klasse FolderController zu.

Ich hatte in einem meiner Module den nachfolgenden Code eingesetzt um aus einem virtuellen Verzeichnis den absoluten Pfad auf dem Server zu ermitteln.

FolderController folderController = new FolderController();
string mappedTargetModuleFolder = folderController.GetMappedDirectory(portalFolder);

Nun sucht man in der neuen Klasse FolderManager aber vergebens nach einer Ersetzung für diese Methode.

Doch gibt es natürlich auch hiefür eine Lösung.

Im Namespace DotNetNuke.Common.Utilities finden wir die Klasse PathUtils und diese Klasse enthält eine Methode MapPath, die genau die Funktionaltität der zu ersetzenden Methode aus der Klasse FolderController erfüllt.

Und so sieht dann der Code aus:

string mappedTargetModuleFolder = PathUtils.Instance.MapPath(portalFolder);

DotNetNuke – GetModuleDefinitionByName – Obsolete

Heute geht es in meiner Serie „Clean Code meiner DotNetNuke Module“ um die Methode GetModuleDefinitionByName() aus dem Namespace
DotNetNuke.Entities.Modules.Definitions.ModuleDefinitionController.

Der Einsatz dieser Methode war immer schone etwas umständlich da man nicht direkt den Friendly Name verwenden konnte sondern dieser erst über den Umweg des DesktopController ermitteln musste.

Um wie im folgenden Beispiel die ModuleDefId einer Moduls ermitteln zu können, musste man 2 verschiedene Controller Klassen ansprechen.

  • ModuleDefinitionController
  • DesktopModuleController

Diese Methode wurde bereits mit der Version 5.0 ersetzt.

Bis zur Version 5 wurde die Methode wie folgt verwendet:


ModuleDefinitionController moduleDefinitionController = new ModuleDefinitionController();
int newModulDefId = moduleDefinitionController.GetModuleDefinitionByName(
desktopModuleController.GetDesktopModuleByName("DNNPortal-Download").DesktopModuleID, 
"DNNPortal-Download").ModuleDefID;

Hier nun der Aufruf mit der neuen Methode:


int newModulDefId = ModuleDefinitionController.GetModuleDefinitionByFriendlyName("DNNPortal-Download").ModuleDefID;

DotNetNuke – PortalSecurity.HasEditPermissions – Obsolete

In der Serie Clean Code meiner DotNetNuke Module geht es Heute um die Methode PortalSecurity.HasEditPermissions().

Ebenfalls seit der DotNetNuke Version 5.0 ist die Methode HasEditPermissions() aus dem Namespace DotNetNuke.Security.PortalSecurity nicht mehr zu verwenden.

Die beschriebene Methode wird dazu verwendet, zu ermitteln ob der angemeldete Benutzer die Rechte zur Bearbeitung für ein bestimmtes Modul besitzt.

Ein Aufruf, der bis zur Version 5.0 wie folgt ausgesehen hat:


if (PortalSecurity.HasEditPermissions(ModuleId, TabId))

sollte nun durch den nachfolgenden Aufruf ersetzt werden.


if(ModulePermissionController.HasModuleAccess(
SecurityAccessLevel.Edit,
"EDIT",
this.ModuleConfiguration))

Mehr folgt in Kürze

DotNetNuke.Entities.Portals.PortalSettings.GetHostSettings() – Obsolete

Auf der Suche nach dem Clean Code meiner DotNetNuke Module werde ich in den nächsten Tagen über einige kleine Migrationsschritte berichten.

Beginnen werde ich in diesem Sinne mit der Ersetzung der bereits seit DotNetNuke Version 5.0 obsoleten Methode GetHostSettings aus dem Namespace DotNetNuke,Entities,Portals.PortalSettings.

Hier ein Code Auszug aus einem DNN Modul, welcher bis zur DotNetNuke Version 5.0 Verwendung so oder ähnlich eingesetzt wurde.

Der Code dient dazu, die in einem Portal erlaubten Dateiendungen zu ermitteln. Oder mit anderen Parametern einfach eine Einstellung auf Host Ebene auszulesen

string fileExtensions = DotNetNuke.Entities.Portals.PortalSettings.GetHostSettings()["FileExtensions"].ToString().ToLower();

Für die obsolete Methode kann man nun folgende beiden Varianten verwenden:


string fileExtensions = HostController.Instance.GetSettingsDictionary()["FileExtensions"].ToString().ToLower();

oder diese variante


string fileExtensions = HostController.Instance.GetString("FileExtensions").ToLower();

Ich persönlich finde die Variante mit GetString() in diesem Fall die angebrachtere.

ContextMenuStrip – Dynamisch Separatoren anzeigen oder ausblenden (C#)

In diesem Artikel möchte ich eine Methode vorstellen mit welcher je nach sichtbaren Menüpunkten eines Kontextmenüs die Separatoren zwischen einzelnen Menügruppen angezeigt (visible == true) bzw ausgeblendet (visibel == false) werden.

Schauen wir uns doch zuerst einmal an um was es dabei wirklich geht.

Hier zuerst das Gesamte Menü mit allen Möglichen Menüpunkten.

Dieses Beispiel stellt das Kontextmenü einer Anwendung dar, die verschiedene Kalendereinträge enthalten kann.

Je nachdem welche Art Kalendereintrag (Planung, Anlieferung, Sperrzeit etc.) ausgewählt wurde und welche Berechtigungen ein Benutzer besitzt werden nur bestimmte Menüpunkte aus dieser Gesamtauswahl angezeigt.

Häufig wird das so geregelt, dass man anstelle die Menüpunkte auszublenden diese nur deaktiviert. In diesem Fall ist es dann nicht notwendig sich Gedanken darüber zu machen einzelne Separatoren für eventuell ganz wegfallende Menügruppen zu entfernen.

Nachfolgend nun einige Abbildungen die anzeigen was passiert wenn man die Menüpunkte ausblendet ohne sich um die Separatoren zu kümmern.

Man beachte die vielen direkt aufeinander folgenden Separatoren, Sieht nicht wirklich Professionell aus.

Nun kann man natürlich die Separatoren ebenfalls im Opening Event manuell Sichtbar oder Unsichtbar machen (setzen der Visible Eigenschaft), dass kann aber je nach Komplexität der Menüstruktur sehr schnell sehr mühsam und sehr fehleranfällig werden.

Genau aus diesem Grund habe ich mir hierzu eine Methode erstellt die sich um diese Aufgabe selbstständig kümmert und je nach sichtbaren Menüpunkten die benötigten Separatoren ebenfalls sichtbar oder unsichtbar macht.

Doch bevor ich die Methode zeige, möchte ich zuerst das Ergebnis anhand der nachfolgenden Abbildungen zeigen. Hier nun die korrekt dargestellten Menüs.

Das sieht doch schon viel besser aus !

So nun aber der Quellcode der Methode.

internal static void SetToolStripSeperatorForContextMenu(ContextMenuStrip contextMenuStrip)
{
	bool isVisible = false;
	int seperatorIndex = 0;
	for (int i = 0; i < contextMenuStrip.Items.Count; i++)
	{
		if (contextMenuStrip.Items[i] is ToolStripSeparator)
		{
			contextMenuStrip.Items[i].Visible = false;
		}

		if (contextMenuStrip.Items[i].Available)
		{
			if (seperatorIndex > 0)
			{
				contextMenuStrip.Items[seperatorIndex].Visible = true;
				seperatorIndex = 0;
			}
			isVisible = true;
		}
		if (contextMenuStrip.Items[i] is ToolStripSeparator)
		{
			if (isVisible)
			{
				seperatorIndex = i;
			}
			isVisible = false;
		}
	}
}

Übrigens die Methode einfach am Ende des Opening Event des Kontextmenüs wie folgt aufrufen.

SetToolStripSeperatorForContextMenu(((ContextMenuStrip) sender));

Und fertig.