Einlesen von “delimited with” Daten – Leicht gemacht mit Hilfe von Custom Attribut

So lange es EDV gibt und wohl auch, so lange es EDV geben wird ist der Austausch von Daten immer wieder ein Thema.

Um Daten zwischen Verschiedenen Systemen auszutauschen sind die Möglichkeiten fast unerschöpflich.

Eine der Möglichkeiten besteht darin Daten in eine Zeichenkennte zu schreiben und die Daten mit einem sogenannten Trennzeichen (Delimiter) zwischen den einzelnen Feldinformationen zu trennen (Auch noch im Zeitalter von Serialisierung und anderen tollen Dingen).

Die meisten Programme können Daten in diesem Format exportieren.

Viel interessanter wird es, wenn man diese Daten in seine eigenen Programmen verarbeiten möchte.

Ich möchte hier eine einfache Möglichkeit zeigen, wie man sich mit ein paar Zeilen Code eine schicke kleine Funktion und ein Custom Attribut schreiben kann die einem dabei behilflich sind, solche Daten einzulesen und diese Daten in Eigenschaften seiner eigenen Klasse zu schreiben.

Hier zuerst das Custom Attribute.  Das bietet schon ein wenig mehr Möglichkeiten als es hier in diesem Beispiel zum Einsatz kommt, aber der Beitrag soll auch nicht eine fertige Lösung darstellen, sondern nur behilflich sein, einen Weg zu zeigen:

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class ImportFieldAttribute : Attribute
{
	/// <summary>
	/// Feldtype der zu importierenden Daten
	/// </summary>
	private readonly ImportFieldType fieldType;

	/// <summary>
	/// Position im Segment
	/// </summary>
	private readonly int position;

	/// <summary>
	/// Initializes a new instance of the <see cref="ImportFieldAttribute"/> class.
	/// Für Import von Sätzen mit Trennzeichen
	/// </summary>
	/// <param name="fieldType">Type of the field.</param>
	/// <param name="position">The position.</param>
	/// <param name="length">The length.</param>
	public ImportFieldAttribute(ImportFieldType fieldType, int position)
	{
		this.fieldType = fieldType;
		this.position = position;
	}

	/// <summary>
	/// Feldtype der zu importierenden Daten
	/// </summary>
	public ImportFieldType FieldType
	{
		get
		{
			return this.fieldType;
		}
	}

	/// <summary>
	/// Position im Segment
	/// </summary>
	public int Position
	{
		get
		{
			return this.position;
		}
	}
}

Schauen wir uns nun den Code an der sich mit Hilfe des Custom Attribute an die Daten ran macht.

public static void ImportClassFieldsFrom(object target, string buf, char delimiter)
{
	var fields = buf.Split(delimiter);

	const BindingFlags Flags = BindingFlags.Instance | BindingFlags.Public;
	var targetProperties = target.GetType().GetProperties(Flags);
	try
	{
		foreach (PropertyInfo propertyInfo in targetProperties)
		{
			var attribs =
				(ImportFieldAttribute[])propertyInfo.GetCustomAttributes(typeof(ImportFieldAttribute), true);

			if (attribs.Length > 0)
			{
				ImportFieldAttribute attrib = attribs[0];
				if (attrib.Position <= fields.Length)
				{
					var temp = fields[attrib.Position];
					propertyInfo.SetValue(target, temp.TrimEnd(), null);
				}
			}
		}
	}
	catch (Exception ex)
	{
		Exceptions.Log.LogError(buf, ex);
	}

}

Und so verwendet man diese Methode und das Custom Attribute:

Bei der Definition der Klasse, welche die Informationen aus dem “Delimited width” Zeichenkette aufnehmen soll, wende ich nun das Custom Attribute wie folgt an:

public class Palettes
{
	/// <summary>
	/// Gets or sets GrossWeight.
	/// </summary>
	[ImportField(ImportFieldType.Char, 1)]
	public string GrossWeight { get; set; }

	/// <summary>
	/// Gets or sets Height.
	/// </summary>
	[ImportField(ImportFieldType.Char, 2)]
	public string Height { get; set; }

	/// <summary>
	/// Gets or sets Lenght.
	/// </summary>
	[ImportField(ImportFieldType.Char, 3)]
	public string Lenght { get; set; }
}

Um die Klasseneigenschaften meiner Klasse mit den Werten aus der “Delimited width” Zeichenkette zu bekommen wird folgender Methodenaufruf durchgeführt:

var palette = new Palettes());
AttributHelper.ImportClassFieldsFrom(palette, "12,00~15,01~23,00", '~');

Fragen, Anregungen und Kritik wie immer, einfach als Kommentar

Fritzbox 7390 – Nortel VPN Client – VPN tunnel is disconnected due to routing table change

Nachdem ich vor einigen Wochen in meinem Homeoffice einen DSL Speedport von der Telekom gegen eine Fritzbox 7390 ausgetauscht habe, kam es bei der Verwendung der Nortel VPN Client Software zu ständigen Verbindungsabbrüchen.

imageDa ich dieses Verhalten kurz nach dem Einsatz der Fritzbox festgestellt habe, lag die Vermutung, dass es mit der Fritzbox zusammenhängt, natürlich mehr als nah.

Um die Vermutung zu verfestigen oder zu wiederlegen, habe ich mit der gleichen Hardware in verschiedenen Netzwerkumgebungen getestet, ob das Problem nicht doch an meinem Rechner oder der Konfiguration liegen könnte.

Das war aber nicht der Fall, das Problem trat nur in meinem Homeoffice auf.

Was lag also näher als den Support von AMV anzuschreiben um ihm meine Probleme zu schildern.

Gesagt getan und folgendes E-Mail an den Support gesendet:

>>>=================  START MAIL vom 03.05.2012 ================>>>

Hallo Support Team,

Ich verwende den Nortel VPN Client Version 10.04.016 um eine VPN Verbindung zu einem Firmennetzwerk herzustellen.

Ich kann die Verbindung herstellen, nach kurzer Zeit (nach einigen Sekunden bis Minuten) wird die Verbindung getrennt und es erscheint folgende

Fehlermeldung:

„VPN tunnel is disconnected due to routing table change“.

Wenn ich den gleichen Rechner in meinem Büronetzwerk verwende in dem ich einen Router von Vodafone einsetze funktioniert der VPN Zugang zum selben Firmennetzwerk den ganzen Tag ohne Unterbrechung.

Auf das VPN des Firmennetzwerk habe ich keinen Einfluss (das ist aber auch obsolet, da es für über 100000 Mitarbeiter weltweit funktioniert.

Für mich auch, wenn ich es nicht mit der FritzBox 7390 aus dem Homeoffice heraus verwende.

Der 7390 ist an einem VDSL 50000 Anschluss angeschlossen und funktioniert bis auf dieses VPN Problem ohne weitere Einschränkung. Leider kann ich ohne dieses VPN mein Homeoffice nicht nutzen 🙁 .

Ich hoffe Sie haben Lösungsansätze für mich

>>>================= ENDE MAIL vom 03.05.2012 ================>>>

Nicht gut aber schnell, nämlich am gleichen Tag habe ich dann eine erste Antwort erhalten

<<<================= START MAIL vom 03.05.2012 ===================

Guten Tag Herr Schelian,

vielen Dank für Ihre Anfrage an den AVM-Support.

Tipps und Hinweise zu VPN-Lösungen anderer Hersteller finden Sie in folgendem

Link:

http://service.avm.de/support/de/SKB/FRITZ-Box-7390/243:VPN-Loesung-eines-andere

n-Herstellers-mit-FRITZ-Box-verbinden

Über die Tipps in unserer FAQ hinaus leisten wir keinen Support für lokale oder individuelle VPN-Szenarien mit der Software anderer Hersteller.

<<<================= ENDE MAIL vom 03.05.2012 ===================

Außer einem Link zu einer furchtbaren FAQ die so überhaupt nichts mit dem von mir geschilderten Problem zu tun hat, nur “Schön dass Sie uns geschrieben haben”.

Aber ich wäre nicht Ich, wenn ich mich damit zufrieden gegeben hätte.

Also habe ich 2 Tage später folgendes Mail an den Support zurückgeschrieben:

==================== START MAIL vom 05.05.2012 ================>>>

Hallo Support Team,

leider hat mir Ihr Hinweis nicht geholfen.

Natürlich hatte ich bevor ich Sie kontaktiert habe die Q&A auf Ihrer Webseite angeschaut.

Ich nehme an, dass Sie mein Problem nicht richtig verstanden haben, ich versuche nicht mit irgendeiner VPN Lösung auf die Fritzbox zuzugreifen, sondern aus dem Netz welches über die Fritzbox über das Internet hergestellt hat auf ein Weltweites VPN Firmennetzwerk zuzugreifen.

Wie ich bereits in meiner ersten Nachricht geschrieben habe, kann ich mit dem gleichen Notebook aus meinem Firmennetz an dem ein Vodafone Router die Verbindung ins Internet herstellt, ohne Problem auf das externe VPN Netzwerk zugreifen.

Über einen Mobilen WLAN Hotspot funktioniert der Zugriff ebenfalls.

Eben nur nicht sobald ich über den Internetzugang gehe der über die Fritzbox hergestellt wird.

Ich hoffe dass Sie mir weiterhelfen können.

==================== ENDE MAIL vom 08.05.2012 ================>>>

Und tatsächlich erhalte ich nach nur 3 Tagen wieder eine Antwort

<<<================= START MAIL vom 05.05.2012 ===================

Guten Tag Herr Schelian,

vielen Dank für Ihre Rückmeldung.

Es tut mir leid, aber wir supporten wie erwähnt keine individuellen lokalen

VPN-Szenarien.

Freundliche Grüße aus Berlin

Jon Doe (AVM Support) (Name wurde auf Wunsch von AVM geändert)

<<<================= ENDE MAIL vom 08.05.2012 ===================

Es tut mir auch leid, ich konnte mir nicht verkneifen noch einmal zurück zu schreiben

==================== START MAIL vom 10.05.2012 ================>>>

Hallo Herr Doe, (Name wurde auf Wunsch von AVM geändert)

wenn das die Meinung von AVM ist!

Ich finde zwar nicht dass es sich bei meinem Problem im ein VPN Problem sondern um ein Router bzw. Firewall Problem Ihres Routers handelt. Aus irgend einem Grund ändert der Router die Routing Table und stört damit die bestendende Verbindung zu meinem VPN Server.

Vor allem wo dies mit verschiedener Firmware mal besser mal schlechter wird.

Mit der letzten Aktualisierung gerade mal wieder schlechter, bzw. mal wieder gar nicht benutzbar.

Dann werde ich wohl keine Fritz Router mehr einsetzen können.

==================== ENDE MAIL vom 10.05.2012 ================>>>

Aber wer denkt, der AVM Support würde aufgeben, der täuscht, ich erhalte also noch einmal eine Mail vom AVM Support

<<<================ START MAIL vom 11.05.2012 ===================

Guten Tag Herr Schelian,

vielen Dank für Ihre Rückmeldung.

Mein Kollege hat hier völlig recht. Es gibt hier keinen kausalen Zusammenhang mit der FRITZ!Box. Lesen Sie sich bitte dies hier in Ruhe

durch:

http://blog.michaelfmcnamara.com/2010/03/nortel-vpn-client-release-10-04-016-for-windows-7/#comment-3602

Dort ist exakt die Fehlermeldung aufgegriffen und auch die Ursache und eine Lösung beschrieben.

Freundliche Grüße aus Berlin

Max Mustermann (AVM Support) (Name wurde auf Wunsch von AVM geändert)

<<<================= ENDE MAIL vom 11.05.2012 ===================

Ich habe dort zwar keine Lösung für mein Problem gefunden, aber immerhin einen Workaround und konnte damit tatsächlich ermitteln, dass das Problem mit dem 7390 nur mit WLAN auftritt und nicht wenn ich den Rechner per Kabel angeschlossen habe.

Eigentlich hätte man die Sache damit bewenden lassen können, aber ich wäre nicht Ich wenn ich das Problem nicht an der Wurzel packen wollte.

Und so habe ich mich auf die Suche nach den Unterschieden in den verschiedenen W-LAN Netzen gemacht.

Um das ganze hier nicht ausufern zu lassen möchte ich nun aber zur Lösung des Problem kommen.

Hallo AVM Support hört gut zu, es gibt eine Fritz7390 Lösung für mein VPN spezifisches Problem.

Unter WLAN–>Funkkanal gibt es eine Einstellung:

image

Für 300 Mbit/s optimierte Kanäle verwenden.

Und diese Option ist im Default gesetzt (Und muss ausgeschaltet werden).

Das einschalten dieser Option führt dazu, dass die Fritzbox anstelle eines einzelnen Kanals einen Doppelkanal zum Beispiel Kanal 8 und Kanal 12 verwendet, und in diesem Zusammenhang kommt es zu wohl irgendwie zum internen Umschreiben der Routing Table oder irgend einem anderen Fritzbox internen Problem, dass zu dem in diesem Artikel beschriebenen Abbruch der VPN Verbindung kommt.

Übrigens konnte ich noch einen Zusammenhang mit dieser Einstellung feststellen.

image

Bei der Musik Übertagung vom iPhone/iPad via AirPlay auf einen Verstärker kam es immer wieder zu kurzen Unterbrechungen (Aussetzern)!

Diese sind nun auch verschwunden.

Hallo AVM Support Team, es war mir eine Freude behilflich sein zu können.

Ich schicke euch den Link per Mail, dann könnt Ihr eure FAQ ergänzen

DotNetNuke – Nach Upgrade auf DNN 6.1.X – HTML Modul speichert keine Änderungen mehr

Heute Vormittag habe ich den Anruf eines Kunden erhalten der mir folgendes Problem geschildert hat.

Nach der Aktualisierung eines DotNetNuke Portals von DNN 5.6.X auf DNN 6.1.1 werden Änderungen die man an Texten im HMTL Modul vornimmt nicht gespeichert.

Keine Fehlermeldung, kein Eintrag im Ereignisprotokoll, einfach nichts!

Nachdem ich mir die Konfiguration genauer angeschaut hatte, konnte ich einen Fehler in der Konfiguration des System (IIS und App Pool) ausschließen.

Das Problem war aber trotzdem relativ schnell gefunden.

Mit dem “früher” als Standardeditor eingesetzten FCK Editor gibt es mit der aktuellen Version (Ich glaube schon seit Version 5.6.X) von DotNetNuke an einigen Stellen Probleme, unter anderem eben auch im Text/HMTL Modul.

Irgendwelche JavaScripte des Editors kollidieren mit anderen JavaScript aufrufen in irgend einer der vielen verwendeten JavaScript Frameworks.

JavaScript eben Smiley

Ich weiß schon warum sich meine Begeisterung für dieses (Java)Script Gedöns in Grenzen hält.

OK, aber lassen wir das.

Ob es einen aktualisierte Version des FCK Editors gibt, die mit DNN Version 6.1 und höher läuft, weiß ich nicht, sollte jemand näheres dazu wissen, würde ich mich über einen entsprechenden Kommentar freuen.

Um das Problem mit den Bordmitteln von DNN zu lösen kann man aber einfach den Texteditor umstellen und anstelle des FCK Editors den Telerik Texteditor, der aktuell als Standardeditor von DotNetNuke verwendet wird.

Hierzu öffnet man die web.config und nimmt folgende Änderung vor:

Den defaultProvider von “FckHtmlEditorProvider” (siehe Vorher) auf “TelerikEditorProvider” (siehe Nachher) ändern

Vorher:

<htmlEditor defaultProvider="FckHtmlEditorProvider">
  <providers>
	<clear />
	<add name="TelerikEditorProvider" type="DotNetNuke.HtmlEditor.TelerikEditorProvider.EditorProvider, DotNetNuke.HtmlEditor.TelerikEditorProvider" providerPath="~/Providers/HtmlEditorProviders/Telerik/" toolsFile="~/Providers/HtmlEditorProviders/Telerik/Config/ToolsDefault.xml" configFile="~/Providers/HtmlEditorProviders/Telerik/Config/ConfigDefault.xml" FilterHostExtensions="True" />
	<add name="FckHtmlEditorProvider" type="DotNetNuke.HtmlEditor.FckHtmlEditorProvider.FckHtmlEditorProvider, DotNetNuke.FckHtmlEditorProvider" providerPath="~/Providers/HtmlEditorProviders/Fck/" CustomConfigurationPath="~/Providers/HtmlEditorProviders/Fck/custom/FCKConfig.js" EnhancedSecurityDefault="false" SecureConfigurationPath="~/Providers/HtmlEditorProviders/Fck/custom/FCKConfigSecure.js" ImageGalleryPath="~/Providers/HtmlEditorProviders/Fck/fckimagegallery.aspx" ImageUploadPath="~/Providers/HtmlEditorProviders/Fck/fckimagegallery.aspx" ImageAllowedFileTypes="gif,png,bmp,jpg" FlashGalleryPath="~/Providers/HtmlEditorProviders/Fck/fckimagegallery.aspx" FlashUploadPath="~/Providers/HtmlEditorProviders/Fck/fckimagegallery.aspx" FlashAllowedFileTypes="fla,swf" LinksGalleryPath="~/Providers/HtmlEditorProviders/Fck/fcklinkgallery.aspx" DynamicStylesGeneratorPath="~/Providers/HtmlEditorProviders/Fck/FCKStyles.aspx" DynamicStylesCaseSensitive="true" DynamicStylesGeneratorFilter="controlpanel|filemanager|mainmenu|wizard" StaticStylesFile="~/Providers/HtmlEditorProviders/Fck/FCKeditor/fckstyles.xml" StylesDefaultMode="dynamic" DynamicCSSGeneratorPath="~/Providers/HtmlEditorProviders/Fck/FCKCSS.aspx" StaticCSSFile="~/Providers/HtmlEditorProviders/Fck/FCKeditor/editor/css/fck_editorarea.css" CSSDefaultMode="dynamic" spellCheck="ieSpell" AvailableToolbarSkins="Office2003,Silver" DefaultToolbarSkin="Office2003" AvailableToolBarSets="DNNDefault,Default,NoGallery,Basic" DefaultToolbarSet="DNNDefault" DefaultImageGallerySkin="Default" DefaultFlashGallerySkin="Default" DefaultLinksGallerySkin="Default" FCKDebugMode="false" UseFCKSource="false" OptionsOpenMode="ShowModalDialog" ShowModuleType="true" FixOldDNNPostback="false" CustomOptionsDialog="Admin" />
  </providers>
</htmlEditor>

Nachher:

<htmlEditor defaultProvider="TelerikEditorProvider">
  <providers>
	<clear />
	<add name="TelerikEditorProvider" type="DotNetNuke.HtmlEditor.TelerikEditorProvider.EditorProvider, DotNetNuke.HtmlEditor.TelerikEditorProvider" providerPath="~/Providers/HtmlEditorProviders/Telerik/" toolsFile="~/Providers/HtmlEditorProviders/Telerik/Config/ToolsDefault.xml" configFile="~/Providers/HtmlEditorProviders/Telerik/Config/ConfigDefault.xml" FilterHostExtensions="True" />
	<add name="FckHtmlEditorProvider" type="DotNetNuke.HtmlEditor.FckHtmlEditorProvider.FckHtmlEditorProvider, DotNetNuke.FckHtmlEditorProvider" providerPath="~/Providers/HtmlEditorProviders/Fck/" CustomConfigurationPath="~/Providers/HtmlEditorProviders/Fck/custom/FCKConfig.js" EnhancedSecurityDefault="false" SecureConfigurationPath="~/Providers/HtmlEditorProviders/Fck/custom/FCKConfigSecure.js" ImageGalleryPath="~/Providers/HtmlEditorProviders/Fck/fckimagegallery.aspx" ImageUploadPath="~/Providers/HtmlEditorProviders/Fck/fckimagegallery.aspx" ImageAllowedFileTypes="gif,png,bmp,jpg" FlashGalleryPath="~/Providers/HtmlEditorProviders/Fck/fckimagegallery.aspx" FlashUploadPath="~/Providers/HtmlEditorProviders/Fck/fckimagegallery.aspx" FlashAllowedFileTypes="fla,swf" LinksGalleryPath="~/Providers/HtmlEditorProviders/Fck/fcklinkgallery.aspx" DynamicStylesGeneratorPath="~/Providers/HtmlEditorProviders/Fck/FCKStyles.aspx" DynamicStylesCaseSensitive="true" DynamicStylesGeneratorFilter="controlpanel|filemanager|mainmenu|wizard" StaticStylesFile="~/Providers/HtmlEditorProviders/Fck/FCKeditor/fckstyles.xml" StylesDefaultMode="dynamic" DynamicCSSGeneratorPath="~/Providers/HtmlEditorProviders/Fck/FCKCSS.aspx" StaticCSSFile="~/Providers/HtmlEditorProviders/Fck/FCKeditor/editor/css/fck_editorarea.css" CSSDefaultMode="dynamic" spellCheck="ieSpell" AvailableToolbarSkins="Office2003,Silver" DefaultToolbarSkin="Office2003" AvailableToolBarSets="DNNDefault,Default,NoGallery,Basic" DefaultToolbarSet="DNNDefault" DefaultImageGallerySkin="Default" DefaultFlashGallerySkin="Default" DefaultLinksGallerySkin="Default" FCKDebugMode="false" UseFCKSource="false" OptionsOpenMode="ShowModalDialog" ShowModuleType="true" FixOldDNNPostback="false" CustomOptionsDialog="Admin" />
  </providers>
</htmlEditor>

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.

SQL Server Management Studio – Saving changes is not permitted

Heute bin ich wieder mal (jedes mal wenn ich das Management Studio neu installiere) über ein bereits seit langem bekanntes Problem gestolpert.

Nachdem ich einer vorhandenen Tabelle  einige Felder über den Table Designer hinzugefügt habe und diese Änderungen speichern wollte, habe ich folgende Meldung erhalten:

Saving changes is not permitted. The changes you have made require the<br />
following tables to be dropped and re-created, You have either made changes<br />
to a table that can’t be re-created or enabled the option Prevent saving<br />
changes that require the table to be re-created,

Verzeihung liebes Management Studio, Nein ich möchte nicht dass du die Tabelle löschst und sie mit der neuen Struktur Neu erstellst.

Da mir aber zu diesem Zeitpunkt wieder mal entfallen war, dass ich dieses Problem früher schon hatte, habe ich mich kurzerhand an meine Timeline in Twitter gewandt.

Dort habe ich prompt, wie fast immer,  die richtige Antwort erhalten.

Danke dafür an Sebastian und Norbert, bei den beiden Funktioniert das Gedächtnis wohl noch besser als bei mir.

Und damit ich in Zukunft selbst daran denke, und auch Uwe nachschauen kann wie man das Problem lösen kann, habe ich diesen Beitrag geschrieben.

Und hier nun die Lösung des Problems:

Man kann in den Optionen einstellen, ob das Management Studio diese Vorgehensweise “Warnung ausgeben und das Drop/Create Szenario” anstelle einer “Änderung der Tabellenstruktur”, verwenden soll (Siehe nachfolgende Abbildung).

SNAGHTML15e0776

Vielleicht hilft das hier nicht nur mir und Uwe

SugarSync – Windows 7 64 BIT Explorer friert ein und stürzt ab

Bevor ich zum eigentlichen Problem mit SugarSync und dem Explorer Absturz komme möchte ich kurz Beschreiben wie und warum ich zu SugarSync gekommen bin.

Meine Geschichte zu SugarSync

imageVor einigen Wochen, eigentlich sind es schon Monate her, bin ich auf das Synchronisations Tool SugarSync aufmerksam geworden.

Dieses Tool stellt ähnliche Funktionalitäten wie die Dropbox oder Live Mesh zur Verfügung.

Da ich sowohl Dropbox als auch Live Mesh einsetze, und das nicht weil ich gerne mehrere Tools für ein und denselben Zweck einsetze, sondern weil jedes der Tools ein Alleinstellungsmerkmal hat, dass ich benötige musste ich beide verwenden.

Alleinstellungsmerkmal Dropbox (gegenüber Live Mesh)

imageBei der Dropbox war das Alleinstellungsmerkmal die Möglichkeit “Public Links” erstellen zu können, die man einfach weiter geben kann, damit ein anderer sich die mit dem “Public Link” verknüpfte Datei herunterladen kann.

Alleinstellungsmerkmal Live Mesh (gegenüber Dropbox)

imageBei Live Mesh, ist es die Möglichkeit beliebige Ordner aus dem Dateisystem in die Synchronisation aufzunehmen und mit beliebigen Ordner auf anderen Geräten synchronisieren zu können.

SugarSync Alleinstellungsmerkmal (gegenüber Live Mesh und Dropbox)

SugarSync, kann sowohl “Public Links” erstellen, als auch beliebige Ordner des Dateisystems synchronisieren. Damit würden meine beiden speziellen Anforderungen von einem Tool erfüllt werden und ich müsste nicht auf mehrere Tools zurückgreifen.

Nach der Installation – der erste Start

Nachdem ich also überzeugt war, dass ich durch SugarSync die beiden Produkte Live Mesh und Dropbox ersetzen zu können, habe ich SugarSync auf 4 Geräten installiert.

  • 2 Geräte mit Windows 7 64 Bit
  • 1 Gerät mit Windows 7 32 Bit
  • 1 Gerät mit Windows XP 32 Bit

Auf 3 der 4 Geräte funktionierte alles wie erwartet, auf dem 4ten Gerät, das leider mein aktueller Entwicklungsrechner (Windows 7 64 Bit) war, trat das folgende Problem auf:

Der Versuch, nachdem der Rechner neu gestartet wurde, ein Windows Explorer Fenster zu öffnen führte dazu, dass dieses einfriert und nach einigen Sekunden abstürzt.

Neben dem Absturz des Explorer Fenster wurden auch einige Programme aus der Symbolleiste beendet:

clip_image002

Da ich mir nicht erklären konnte warum dieses Problem auftritt, ich aber sicher war, dass ich nicht der einzige sein kann, der dieses Problem hat, habe ich eine Mail an den Support geschrieben, und gehofft, der würde mir eine Antwort geben können.

imageDie Support Odyssee

Am 26.10.2011 habe ich dann eine ausführliche Beschreibung meines Problems per E-Mail an den Support gesendet.
Da SugarSync (sowohl das Produkt als auch die Webseite) auch auf Deutsch verfügbar ist, hatte ich ohne groß darüber nachzudenken, dass E-Mai in Deutsch geschrieben.

Am 01.11.2011 habe ich dann eine Nachricht vom Support erhalten (in Englisch), dass Sie das Ticket geschlossen haben, da ich mich nicht weiter gemeldet habe. Erstauntes Smiley

Am 02.11.2011 erhalte ich dann aber ein Mail vom Support, indem Sie mir erklären, dass ich doch bitte mein Problem in Englisch an sie senden soll, da der Support nur in English gegeben werden könnte.

Ich hatte in den folgenden Tagen wenig Zeit und habe mich daher erst wieder am 23.11.2011, nun in Englisch, an den Support gewendet und mein Problem in einer ausführlichen Mail beschrieben und darum gebeten mir mitzuteilen, wie man ein Error Logging einschalten könnte, damit ich mehr Informationen zu dem Problem liefern könnte.

Immerhin habe ich nun am gleichen Tag eine (automatisierte) Antwort bekommen, dass meine Mail angekommen sei und sich “ASAP” jemand um mein Problem kümmern würde.

Am 01.12.2011 (ist das ASAP) erhalte ich dann eine Mail, dass sie sich überhaupt nicht vorstellen können wie das Problem auftreten könnte, sie aber gerne mal Remote auf meinen Computer schauen würden um das Problem zu analysieren.

Ich habe dann kurz geantwortet, dass ich davon keinen Gebrauch machen möchte. Damit war klar ich werde das Problem selbst angehen, sobald ich etwas Zeit dafür habe.

Die Zeit war Reif für eine Analyse

imageDa ich SugarSync auf verschiedenen Rechnern installiert und das Problem in dieser Art nur auf einem Rechner aufgetreten ist, habe ich zuerst versucht die Besonderheit dieses Rechners zu ermitteln.

Hierbei habe ich festgestellt, dass ich einen Rechner mit exakt den gleichen installierten Programmen habe, auf dem das Problem nicht auftritt. Der einzige Unterschied, der Rechner auf dem es läuft ist ein 32 Bit Windows.

Also tritt das Problem vermutlich nur auf einem 64 Bit System auf.

Einer der installierten Rechner auf dem es läuft ist ein Windows 7 64 Bit Rechner. Dann vergleiche ich doch mal die darauf installierten Programme.

Bei Durchsicht der installierten Programme bekomme ich relativ schnell eine Ahnung woran das Problem liegen kann.

imageAuf dem Rechner mit dem Problem ist neben SugarSync auch TortoiseHg installiert, das ist auf dem anderen Windows 7 64 Bit Rechner nicht (mehr) installiert.

Ich möchte kurz erklären warum meine Vermutung relativ schnell in diese Richtung ging:

Das Stichwort lautet: Overlay Icons.

Sowohl TortoiseHg als auch SugarSync verwenden Overlay Icons um den Dateistatus im Windows Explorer darstellen zu können.

Wie sich nachfolgend herausstellen sollte, liegt tatsächlich das Problem an der Verwendung dieser Overlay Icons.

Die Lösung (vorübergehend auf jeden Fall)

Sowohl TortoiseHg als auch SugarSync bieten die Möglichkeit die Darstellung der Overlay Icons ein und ausschalten zu können.

Man kann eine der beiden Overlay Icon Anzeigen deaktivieren, und das Problem tritt nicht mehr auf. (Ich werde natürlich dieses Problem nun an SugarSync melden, damit sie das Problem aktiv angehen können, mal schauen ob und wann das dann geschieht)

Hier die Einstellungen die man ändern muss damit die beiden Produkte nebeneinander auf dem gleichen Rechner funktionieren.

Entweder dieses Option ausschalten (SugarSync):

SNAGHTML6d562c

Oder diese hier (TortoiseHg) ausschalten:

SNAGHTML6e7984

Und nun mal schauen ob der “Live Test” mit SugarSync meine Bedürfnisse ganz befriedigen kann und ich dann in naher Zukunft die beiden anderen Produkte ganz von meinen Rechnern entfernen kann.

PHP Debugging für .NET Dummies (IIS – WordPress – PhpStorm – Xdebug)

Vor einigen Tagen stand ich vor der Aufgabe im Zuge eines kleinen Projektes eine Verbindung zwischen der .NET und der PHP Welt herstellen zu müssen.

Als .NET Entwickler bin ich es eigentlich gewohnt mit Visual Studio zu arbeiten. Auch wenn man nicht immer mit VS super zufrieden ist, kann man damit aber doch ganz ordentlich Entwickeln. daher war es mir  wichtig auch für diesen PHP Ausflug zuerst einmal eine “gescheite” Entwicklungsumgebung für dieses Projekt aufzubauen.

Da ich neben ReSharper (Visual Studio Plugin) bereits WebStorm (beides Erstklassige Produkte von JetBrains) einsetze war für mich sehr schnell klar, dass als PHP IDE PhpStorm vom gleichen Hersteller zum Einsatz kommen soll.

Unter einer “gescheiten” Entwicklungsumgebung verstehe ich ab er als minimale Anforderung:

  • Einen guten Editor (IDE) (Am besten Syntax highlighting)
  • Einen ordentlichen Debugger (Am besten in der IDE integriert)

Mit PhpStorm habe ich die “gute”  IDE bereits ausgewählt.

Das Problem des integrierten Debugger kann ich aber weder mir PhpStorm noch mit einer anderen mir bekannten Entwicklungsumgebung für PHP direkt und einfach lösen.

Um PHP zu debuggen gibt es eine Extension mit der Bezeichnung “Xdebug”,  die direkt zusammen mit PHP arbeitet und in der PHP.INI konfiguriert werden muss.

Und genau diese Konfiguration und Einrichtung ist mir als .NET Entwickler irgendwie nicht wirklich einfach von der Hand gegangen.

Die Konfiguration von Xdebug in PHP war noch recht einfach und auch ganz ordentlich dokumentiert. Wie man jedoch aus der IDE (PhpStorm) Breakpoints setzen und den Debugger nutzen kann, dass blieb zuerst ein Buch mit vielen Siegeln.

Über die Konfiguration von PHP und Xdebug auf einem IIS unter Verwendung von PhpStorm, gab es eigentlich keine oder nur unvollständige Anleitungen (oder ich habe sie einfach nicht gefunden).

Nun aber genug der Vorreden, ab hier geht es nun um die Beschreibung (Schritt für Schritt Anleitung) wie man auf einem Windows Rechner eine komplette Entwicklungsumgebung für PHP einrichten kann, mit der sogar ein .NET Entwickler etwas anfangen kann 🙂 .

Systemumgebung, Programme, Voraussetzungen:

  • Windows 2008 R2 Server (Windows 7 mit installiertem IIS würde auch funktionieren)
  • WordPress (Oder jede andere PHP Anwendung wie z.B. Joomla)
  • imagePhpStorm 3.X
  • Xdebug (Debugger Tool für PHP)

Installation / Überprüfung IIS

Benötigt wird ein IIS 7 oder 7.5 (kein IIS Express).

Unter dem W2K8 geht das einfach indem man die Serverrolle Webserver (IIS) auswählt und installiert.

image

Es werden keine speziellen Rollendienste benötigt, also einfach nur auf Weiter und Installieren klicken bis die Installation abgeschlossen ist.

image

WordPress (und alle benötigten Komponenten installieren)

Der einfachste Weg um ein Produkt wie WordPress auf einem Windows Rechner zu installieren ist sicherlich die Verwendung des Web Plattform Installer (Der übrigens auch in WebMatrix integriert ist).

Der Web Plattform Installer stellt selbst fest welche Komponenten auf dem Rechner vorhanden sind und welche installiert werden müssen um das ausgewählte Produkt verwenden zu können.

Hier kann man dem Web Plattform Installer herunterladen

Also Web Plattform Installer herunterladen und aufrufen (installieren).

Nach der Initialisierung geben wir im Suchfeld “WordPress” ein und bestätigen die Eingabe mit der Enter Taste.

Nun wählen wir WordPress in der gewünschten Sprache aus und betätigen den Button hinzufügen.

image

Wenn man nun auf Installieren klickt werden die folgenden Tools und Produkte installiert.

image

Während der Installation müssen wir noch folgende Angaben machen:

image

Da ich eine Entwicklungsumgebung aufbauen möchte, werde ich MySQL auf dem lokalen Rechner installieren lassen. (Weiter klicken)

Nun bekommt man noch mal alle zu installierenden Produkte angezeigt und wird aufgefordert den Lizenzbedingungen zuzustimmen.

image

Jetzt muss noch ein root Passwort für die MySQL Installation angeben werden:

Beim Weiter Klicken, geht die Installation schon los.

Die Installation kann je nach Internet Geschwindigkeit unterschiedlich lange dauern, da während der Installation die aktuellen Pakete heruntergeladen und installiert werden.

Bei DSL 16000 und einem Rechner mit I7 Prozessor dauert die Gesamte Installation 3 – 5 Minuten.

Nächster Stop Webseite für WP konfigurieren:

image

Da ich WordPress nur in der Entwicklungsumgebung aufrufen und nicht von extern zugänglich machen möchte, lasse ich die Einstellungen wie vorgegeben. Das führt nach der Installation dazu, dass die Seite unter localhost/wordpress erreichbar sein wird.

Noch ein Stop Datenbank erstellen

image

Datenbank auswählen / erstellen und Root Passwort

image

Da wir MySQL gerade erst installiert haben, müssen wir natürlich eine Neue Datenbank erstellen.

Im Eingabefeld (roter Pfeil(, müssen wir das Root Passwort eingeben, dass wir während der Installation von MySQL vergeben haben.

Datenbankbenutzer / Passwort eingeben

image

Die Einträge mit den grünen Pfeilen müssen wir nicht anpassen.

In den Feldern mit den roten Pfeilen muss nun noch ein Kennwort für den Datenbank Benutzer eingegeben werden. Das muss nicht, kann aber das gleiche wie das Root Passwort sein (aber bitte nur auf lokalen Entwicklungssystemen, sonst sollten die Datenbank Passwort nicht dem Root Passwort entsprechen)

In allen weiteren Felder müssen keine Eingaben gemacht werden.

image

image

Damit sind nun WordPress und alle benötigten Tools und Programme installiert.

WordPress sollte nun schon funktionieren, also testen wir es mal.

Browser öffnen und localhost/wordpress eingeben.

image

Am besten geben wir gleich die notwendigen Daten für WP ein, so dass wir später direkt auf die Homepage von WordPress und nicht mehr auf die Admin Setup Seite geleitetet werden.

Beim nächsten Aufruf sollte das so aussehen:

image

Xdebug installieren

Nun müssen wir die Xdebug Extension installieren.

Wie wir weiter oben sehen wurde PHP in der Version 5.2 installiert, also müssen wir nun auch die Xdebug Version für PHP 5.2 herunterladen und installieren.

Aber Achtung es gibt 2 verschiedene Varianten:

Eine Thread sichere und eine Nicht Thread Sichere (nts), für die Installtion auf dem IIS muss die nts Variante verwendet werden.

Hier geht es direkt zum Download xdebug version 2.1.2 für PHP 5.2 Not Thread Safe

Die Extension besteht lediglich aus einer DLL Datei mit dem Namen:

php_xdebug-2.1.2-5.2-vc6-nts.dll

Nachdem man die Datei heruntergeladen hat muss diese in das Ext Verzeichnis von PHP kopiert werden.

Bei einer Standardinstallation auf einem W2K8 Server (64 BIT) findet man das Ext Verzeichnis unter folgendem Pfad:

C:\Program Files (x86)\PHP\v5.2\ext

Als nächstes muss die PHP.INI angepasst werden um die Debugger Extension verwenden zu können

Die PHP.INI befindet sich im Verzeichnis:

C:\Program Files (x86)\PHP\v5.2

Also öffnen wir die PHP.INI mit einem Texteditor und fügen folgende Einträge hinzu:

[xdebug]
zend_extension = "C:\Program Files (x86)\PHP\v5.2\ext\php_xdebug-2.1.2-5.2-vc6-nts.dll"
xdebug.remote_enable = On
xdebug.remote_host = "localhost"
xdebug.remote_port = 9000
xdebug.remote_handler = "dbgp"

Eigentlich ist es gleich an welcher Stelle diese Einträge hinzugefügt werden, aber wer sich gerne strikt an Vorgaben hält, kann die Einträge direkt unter dem folgenden Eintrag einfügen, dann funktioniert es auf jeden Fall.

image

Nun speichern wird die PHP.INI und müssen noch den IIS Neu starten:

Entweder über die Konsole “Internetinformationsdienste (IIS)-Manager

oder über die Kommandozeile:

net stop WAS (zum stoppen des IIS)

und dann

net start W3SVC (um den IIS wieder zu starten.)

Später (nachdem wir PhpStorm installiert haben und damit auch einen brauchbaren Editor  zur Verfügung haben)  prüfen wir auch noch ob die Extension richtig eingerichtet wurde.

PhpStorm installieren

PhpStorm gibt es in einer 30 Tage Trial Version, wer also keine Lizenz besitzt kann sich  erst einmal 30 Tage lang anschauen ob im PhpStorm das Geld, was es kostet, wert ist (Ich denke dass ist es auf jeden Fall, ein tolles Produkt wie eigentlich alle Produkte von JetBrains. Nein ich bekomme keine Prozente 🙂 )

Den Download gibt es hier: http://www.jetbrains.com/phpstorm/download/

Nachdem herunterladen installieren wir PhpStorm einfach mit allen Standardeinstellungen.

WordPress Projekt in PhpStorm einrichten

Nun öffnen wir PhpStorm und wählen die nachfolgende Option aus:

Create New Project from Existing Files

image

Das Szenario: Lokaler Web Server und Source Code unterhalb des Web Root passt genau für unser vorhaben.

image

Nun wählen wir das Root Verzeichnis (Nicht das WordPress Verzeichnis auswählen) des Default WEB unseres localhost aus.

image

Jetzt geben wir dem Projekt Server noch einen Namen (z.B. MyLocalServer)

image

In nächsten Schritt (Siehe Abbildung oben) geben wir nun die Projekt URL unserer WordPress Installation an.

Und haben damit das Projekt eingerichtet (Wie man auch in der folgenden Abbildung sehen kann).

Xdebug überprüfen

Wir öffnen die Datei index.php aus dem Root Verzeichnis unserer WordPress Installation im PhpStorm Editor.

Dort fügen wir die Funktion phpInfo() direkt nach dem Kommentar ein und speichern die Datei ab.

image

Nun öffnen wir den Browser und geben localhost/wordpress ein. Wenn anstelle der normalen WordPress Startseite diese PHP Info Seite angezeigt wird, haben wir bis hierhin schon mal alles richtig gemacht.

image

Um die Prüfung von Xdebug durchführen zu können, benötigen wir den Source Code der im Browse dargestellten phpinfo Seite.

Dazu lassen wir uns den Quelltext der Seite anzeigen, markieren den gesammten Quelltext, kopieren diesen in die Zwischenablage, und öffnen anschließend folgende Webseite:

http://xdebug.org/find-binary.php

image

Dort wo der rote Pfeil hinzeigt fügen wir dann den in der Zwischenablage befindlichen Quelltext ein und betätigen anschließend den “Analyse my phpinfo() output” Button.

Wenn Xdebug richtig installiert ist, sollte das Ergebnis wie folgt aussehen:

image

Wenn Xdebug nicht richtig installier ist, sollte man überprüfen, ob die Pfadangabe und der Dateiname der Xebug DLL in der PHP.INI richtig angegeben sind.

PhpStorm für das Debugging konfigurieren

image

Wir öffnen den Dialog “Edit Configurations”

image

Nun fügen wir eine neue Konfiguration hinzu:

image

Eine PHP Web Application Konfiguration

image

Im Feld unnamed geben wir unserer Konfiguration einen Namen. Zum Beispiel IIS.

Dann müssen wir einen Server auswählen (da wir aber noch keinen Konfiguriert haben, müssen wir zuerst einmal einen neu anlegen).

Hier Dialog zum einrichten eines neuen Server öffnen:

image

Dann im Dialog auf das + um einen neuen Server anzulegen:

image

Hier der bereits ausgefüllte Dialog:

image

Eingabe bestätigen und anschließend folgende Eingaben noch ergänzen, siehe Beschreibung unterhalb der Abbildung.

image

Der Konfiguration einen Namen geben (z.B. LocalIISWordPress)

Und die StartUrl um WordPress ergänzen, so dass der korrekte Link (http://localhost/wordpress) unter der Start URL angezeigt wird.

Das sollte dann so aussehen:

image

Dann bestätigen wir auch diesen Dialog

Und wenn wir nun auf den RUN Button drücken ….

image

Sollte sich der Default Browser mit der WordPress Seite automatisch öffnen.

Eigentlich sollte das setzen von Breakpoint nun auch schon funktionieren, probieren wir das doch mal indem wir einen Breakpoint auf die Zeile phpinfo() setzen die wir in der index.php eingefügt haben.

image

Einen Breakpoint kann man übrigens ganz einfach dadurch setzen, indem man mit der Maus an die Stelle klickt auf die der Rote Pfeil zeigt.

Und wenn wir nun anstelle des RUN Button, den DEBUG Button drücken …..

image

Richtig, der Programmablauf bleibt am Breakpoint stehen und wartet darauf das wir den Programmablauf fortsetzen.

So und nun viel Spaß beim PHP Debugging.

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)