Archiv der Kategorie: .NET

Verwenden von Feature-Toggle-Patterns, um der Merge-Hölle zu entgehen

Die Merge-Hölle

Bei der professionellen Software-Entwicklung ist eine Versionsverwaltung nicht mehr wegzudenken. Fast alle Versionskontroll-Systeme unterstützen Branches, in denen man beispielsweise die Entwicklung von Features vornimmt, um den Code im Hauptzweig (trunk) nicht zu beeinflussen und unabhängig das Feature entwickeln zu können:

Hier ist ein Beispiel dafür, wie ich bisher die Produkt- / Projektentwicklung kannte: Nachdem eine Produkt-Version released wurde, werden Feature-Branches angelegt, um die Features unabhängig vom Entwicklungs-Branch zu entwickeln. Das hat sehr viele Vorteile: Beispielsweise könnte sich das Produktmanagement entscheiden, ein Feature erst in einer späteren Version zu integrieren, falls sich die Entwicklung verzögern sollte. Zudem kann die Entwicklung eines Features unabhängig von der Entwicklung anderer Features stattfinden.

Wer so arbeitet, kennt auch den Begriff der „Merge-Hölle“. Diese tritt nämlich dann auf, wenn die Entwicklungs-Branches für unterschiedliche Features und der trunk im Code zu weit auseinander laufen oder Code in der gleichen Datei mehrfach umgebaut wurde. Dann nämlich funktioniert der Merge nicht mehr so einfach:

Wenn es sich dabei nur um eine Code-Datei handelt, ist das zwar ärgerlich, aber noch akzeptabel. Die Merge-Hölle ergibt sich, wenn sehr viele Dateien in unterschiedlichen Entwicklungszweigen verändert wurden und vielleicht sogar noch neue Dateien mit gleichem Namen angelegt wurden. Dann funktioniert in der Regel der Merge nicht mehr und man verbringt Stunden und Tage damit, die Konflikte aufzulösen und hofft, dass danach noch alles so funktioniert, wie es man gedacht war.

Weiterhin kann es passieren, dass Probleme, die in einem Branch gefixt wurden, durch den Merge wieder verloren gehen.

Das Feature Toggle Pattern

Hier kann das Feature-Toggle-Pattern Abhilfe schaffen:

Anstelle der Verwendung unterschiedlicher Entwicklungs-Branches findet sie Entwicklung immer auf dem gleichen Branch (trunk) statt. Martin Fowler hat in seinem Artikel „Feature Toggle“ (http://martinfowler.com/bliki/FeatureToggle.html) detailliert beschrieben, wie das Feature-Toggle Pattern funktioniert.

Damit ist es möglich, auch noch „experimentellen“, noch nicht fertigen Code in Produktivsystemen zu releasen. Man muss quasi nur sicherstellen, dass der unfertige Code nie ausgeführt wird. Dadurch arbeitet jeder immer auf dem aktuellen Entwicklungszweig und kann Änderungen an der Codebasis registrieren und darauf reagieren.

Doch wie setzt man das Feature Toggle Pattern in der Praxis um?

Wichtig ist, dass die Stelle, an der das Feature aktiviert bzw. deaktiviert wird, später einfach zu finden und zu entfernen ist, wenn das Feature „produktiv“ geschaltet wird. Wenn ein Schalter nicht entfernt wird, bürdet man sich unnötigerweise „technische Schulden“ auf. Am einfachsten geht das beispielsweise mit einer (abstrakten) Klasse, die eine Property „IsEnabled“ zur Verfügung stellt und von der alle Feature Schalter erben:

internal
abstract
class
FeatureSwitch

{


public
abstract
bool
IsEnabled { get; set; }

}

 

Von dieser Klasse FeatureSwitch leitet man nun einfach einen Schalter für das eigene Feature („MyFeature“) ab:

internal
class
MyFeatureSwitch : FeatureSwitch

{


private
bool
_isEnabled = true;

 


public
override
bool
IsEnabled

{


get { return
_isEnabled; }


set { _isEnabled = value; }

}

}

Hier wird das Feature by-default eingeschaltet (_inEnabled = true). Genauso einfach kann das Feature auch wieder deaktiviert werden.

Im Code verwendet man den Schalter dann so:

MyFeatureSwitch myFeature = new
MyFeatureSwitch();

 

if (myFeature.IsEnabled)

{


// implement feature code here …

}

else

{

 

}

 

Man instanziiert einfach an einer beliebigen Stelle, an der das Feature verwendet wird, den FeatureSwitch. Dort prüft man dann über die Eigenschaft „IsEnabled“, ob das Feature „scharf geschaltet“ ist und implementiert den Code so, als wenn das Feature produktiv verwendet wird.

Alternativ zur abstrakten Basisklasse „FeatureSwitch“ könnte man auch einen ConfigSwitch verwenden wie ich es beispielsweise im Blog von Federik Normen gesehen habe: http://weblogs.asp.net/fredriknormen/archive/2013/09/28/merge-hell-and-feature-toggle.aspx

ProgressBar für die Console

Für ein kleines Projekt brauchte ich einen Fortschrittsbalken für eine Kommandozeilen-Applikation. Ich war erstaunt, wie einfach das funktionierte und möchte meine Erfahrungen hier teilen.

Hier schon mal ein Blick auf das Endergebnis:

image

Zuerst einmal müssen die Begrenzungen des Fortschrittsbalken gezeichnet werden.

Um den Cursor zu positionieren, kann auf die statischen Eigenschaften CursorTop und CursorLeft der Console zurückgegriffen werden:

CursorTopLeft

Hier der Code zum Erstellen der Begrenzungen des Fortschrittsbalkens:

// Create boundaries for progress bar
Console.CursorTop = topOffset;
Console.CursorLeft = leftOffset;
Console.Write("[");
Console.CursorLeft = length;
Console.Write("]");

Das sieht dann erstmal so aus:

image

 

Dann füllen wir die Begrenzungen mit dem aktuellen Fortschritt:

// Calculate size of one percent element
float progressElementSize = ((float)length – leftOffset – 2) / total;

// Create progress content
int position = 1;
Console.CursorLeft = position;
for (int i = leftOffset; i < progressElementSize * progress; i++)
{
    Console.BackgroundColor = progressColor;
    Console.CursorLeft = position++;
    Console.Write(" ");
}
Console.BackgroundColor = ConsoleColor.Black;

Und das sieht dann so aus:

image

Ganz wichtig: In der letzten Zeile wird die Farbe wieder auf schwarz gesetzt, da sonst die aktuelle Schriftfarbe auf die des Fortschritts gesetzt wird.

Abschließend will ich noch den aktuellen Fortschritt prozentual anzeigen:

// Write progress message
Console.CursorLeft = length + 2;
int result = progress * 100 / total;
Console.Write(customProgressMessage ?? (result + "%").PadLeft(4));

Das .PadLeft(4) habe ich angehängt, damit der String nicht springt, wenn von 9% auf 10% und von 99% auf 100% gesprungen wird.

Viel Spaß damit Smiley

Download: http://sdrv.ms/10HH7Pk

Die DotNet Cologne 2013 (#dnc13)

2013-05-03 09.17.38Dieses Jahr war die dotnet cologne 2013 eine besondere Konferenz für mich: ich habe erstmals auf einer Entwickler-Konferenz einen Vortrag gehalten. Das Thema war “Einstieg in das Windows Installer XML (WiX) Toolset”.

Der Raum war wirklich gut gefüllt und es hat mir wirklich viel Spaß gemacht. Auch Dank der freundlichen Unterstützung meines überraschenden  “Co-Referenten” Sebastian Seidel zu den Fragen während des Vortrags war die Session aus meiner Sicht ein Erfolg.

Die Folien habe ich auf SlideShare hochgeladen:
http://de.slideshare.net/minibrain/einstieg-in-das-windows-installer-xml-wix-toolset

Die gezeigten Codebeispiele liegen auf github:
https://github.com/minibrain/DNC13

Nach dem Vortrag gab es noch zahlreiche interessante Diskussionen zum Thema Windows Installer, MSI und Windows Installer XML Toolset. Vielen Dank an dieser Stelle nochmals an alle Teilnehmer!

Während meines Vortrags gab es noch weitere Vorträge, die mich sehr interessiert hätten. “Hackers Reverse Engineering Uncovered” von Rüdiger Kügler war einer davon. Glücklicherweise hat sich im Gespräch später am Stand von WiBu Systems ergeben, dass demnächst der Vortrag in einem webcast wiederholt wird. Das werde ich mir dieses Mal nicht entgehen lassen Smiley

Die Lunch-Session und die folgende Session stand ganz im Zeichen von Usability und User Experience. War wirklich mal interessant, einen Einblick in einen Entwicklungsprozess zu bekommen, der einen Focus auch auf Usability hat. Der Vortrag “WPF UI Development Best Practices” drehte sich im ersten Teil auch um Usability und besprach dann WPF Best Practices natürlich mit Focus auf Usability!

Timur Zanagar hat dann im Vortrag zu “Mobile .NET Entwicklung mit Xamarin 2.0” die Entwicklung mobiler Applikationen für iOS, Android, Windows Phone und MacOS unter Xamarin 2.0 gezeigt. Auch hier haben die zahlreichen Diskussionen zu einer wirklich guten Atmosphäre geführt. Hier muss ich unbedingt mal ein wenig Zeit investieren und eine eigene Android App basteln Smiley

Den Abschluss meiner .NET Cologne 2013 machte der Vortrag von Dennis Traub “Strategischer Anwendungsentwurf mit Domain-Driven Design”. Wirklich interessant, aber schon etwas schwere Kost zum Abschluss.

Alles in allem war diese dotnet cologne 2013 für mich noch etwas besser als die letztes Jahr. Die Organisatoren Albert Weinert, Roland Weigelt, Stefan Lange und Melanie Eibl haben wieder einmal eine tolle Konferenz organisiert. Dankeschön nochmals an dieser Stelle!

Drag&Drop: FileDrop aus eigener Applikation heraus

Möchte man per Drag&Drop Dateien aus dem Windows Explorer in die eigene Applikation hereinziehen, so ist das kein Problem:

  • Platzieren eines Controls, auf dem die Datei(en) per Drag&Drop gezogen werden sollen
  • Eigenschaft “AllowDrop” des Controls auf “True” setzen:

image

  • Registrieren auf die Events DragEnter und DragDrop:

image

  • Im EventHandler für DragEnter prüfen, ob es sich im Dateien und den Mauszeiger anpassen handelt:

if (e.Data.GetDataPresent("FileDrop"))
{
    e.Effect = DragDropEffects.Copy;
}

  • Im EventHandler für DragDrop die Datei auslesen (und in einer TextBox darstellen)

string[] files = e.Data.GetData("FileDrop") as string[];
if (files == null) return;

_txtContent.Text = File.ReadAllText(files[0]);

Das Resultat kann dann so aussehen:

image

Anders herum ist es jedoch etwas schwieriger: Wie kann ich Daten beispielsweise aus einer TextBox per Drag&Drop in eine Datei in den Windows Explorer droppen?

Dazu muss man sich eines kleinen Tricks behelfen: Man muss ein FileDrop initiieren und die zu droppenden Daten in eine temporäre Datei kopieren. Der Dateiname der temporären Datei ist dann auch der Dateiname des Drop Targets. Hier ist eine kleine Schritt-für-Schritt Anleitung:

  • Registrieren auf das MouseDown Event als Initiator für die Drag&Drop Aktion:

image

  • Anlegen einer temporären Datei mit dem Inhalten, die in die zu droppende Datei geschrieben werden sollen. Wichtig: Der Dateiname ist auch der Dateiname der dort erstellt wird, wo die Datei im Explorer gedropped wird:

// Create temporary file name
string tempFile = Path.Combine(Path.GetTempPath(), "LoremIpsum.txt");

  • Inhalt in die temporäre Datei schreiben

// Write content to drop into the temporary file
File.WriteAllText(tempFile, _txtContent.Text);

  • Temporäre Date in ein string-Array verpacken (wird benötigt, da FileDrop ein string array erwartet):

// Create string-array for FileDrop operations
string[] files = new[] { tempFile };

  • DataObject erstellen und mit dem Format der Daten (“FileDrop”) und den Daten instanziieren:

// Create DataObject for "FileDrop" and add string[] with temporary file
DataObject dataObject = new DataObject("FileDrop", files);

  • DragDrop initiieren und dataObject übergeben:

// Initiate DragDrop operation
DoDragDrop(dataObject, DragDropEffects.All);

System.IO.FileLoadException: Verwenden von .NET 2.0 Assemblies innerhalb eines .NET 4.0 Prozesses

Verwendet eine .NET 4.0 Assembly .NET 2.0 Komponenten, kommt es zu einer System.IO.FileLoadException mit der detaillierten Meldung „Die Assembly im gemischten Modus wurde während Version v2.0.50727 der Laufzeit erstellt und kann nicht während der 4.0-Laufzeit ohne zusätzliche Konfigurationsinformationen geladen werden.“:

Bei mir lag es einfach daran, dass ich eine UI-Bibliothek verwendete, die für die CLR 2.0 entwickelt wurde, während mein Hauptprojekt in .NET 4.0 entwickelt wurde.

Es gibt mehrere Möglichkeiten, das Problem zu lösen:

–          Über eine Konfigurationsdatei

–          Über das Konfigurierung der Aktivierung via Code

Lösungsmöglichkeit 1: via Konfigurationsdatei

<startup useLegacyV2RuntimeActivationPolicy=“true“>
<supportedRuntime version=“v4.0″/>
</startup>

Die app.config muss natürlich neben der ausführbaren Datei stehen.

Beispiel:

Ausführbare Datei: example.exe
Konfigurationsdatei: example.exe.config

Mark Miller hat in einem Blog-Beitrag (als PDF: marklio – What is useLegacyV2RuntimeActivationPolicy for_) beschrieben, wozu die useLegacyV2RuntimeActivationPolicy nun wirklich gut ist und was sie tut.

Es gibt jedoch Anwendungsfälle, für die man nicht einfach eine Konfigurationsdatei neben die ausführbare Datei legen kann. Ein Beispiel dafür könnte sein, dass eine C-API oder eine COM-API zur Verfügung gestellt wird. Dann kann nicht einfach der ausführbaren Datei, die die C-API verwendet, eine Konfigurationsdatei beigelegt werden.

Lösungsmöglichkeit 2: via Code

Im Blog von Reed Copsey bin ich auf einen interessanten Artikel gestoßen, der genau mein Problem löst: http://reedcopsey.com/2011/09/15/setting-uselegacyv2runtimeactivationpolicy-at-runtime/

Nachfolgend der Code, der für meine Belange (C-API) funktionierte. Jedoch ist die Lösung in Reed Copseys Blog explizit als inoffiziell gekennzeichnet.

public static bool LegacyV2RuntimeEnabledSuccessfully { get; private set; }

staticRuntimePolicyHelper()

{

ICLRRuntimeInfoclrRuntimeInfo =   (ICLRRuntimeInfo)RuntimeEnvironment.GetRuntimeInterfaceAsObject(Guid.Empty,  typeof(ICLRRuntimeInfo).GUID);

try

{

clrRuntimeInfo.BindAsLegacyV2Runtime();

LegacyV2RuntimeEnabledSuccessfully = true;

}

catch (COMException)

{

// This occurs with an HRESULT meaning

// „A different runtime was already bound to the legacy CLR version 2 activation policy.“

LegacyV2RuntimeEnabledSuccessfully = false;

}

}

[ComImport]

[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]

[Guid(„BD39D1D2-BA2F-486A-89B0-B4B0CB466891“)]

private interface ICLRRuntimeInfo

{

void xGetVersionString();

void xGetRuntimeDirectory();

void xIsLoaded();

void xIsLoadable();

void xLoadErrorString();

void xLoadLibrary();

void xGetProcAddress();

void xGetInterface();

void xSetDefaultStartupFlags();

void xGetDefaultStartupFlags();

[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]

void BindAsLegacyV2Runtime();

}

Interessanterweise wird ein statischer Konstruktor verwendet, der unmittelbar nach dem Laden der assembly ausgeführt wird. Dadurch wird der Code implizit von der Runtime ausgeführt und nicht explizit vom Programm selbst. Das sollte ausreichend gut dokumentiert werden, da potentiell nicht jeder im Team den Code sofort versteht und potentiell den Code als „unnütz“ löschen könnte 😉

Die Eigenschaft „LegacyV2RuntimeEnabledSuccessfully“ gibt an, ob das Umschalten der Runtime-Aktivierung funktioniert hatte. In meinem Fall habe ich eine Ausgabe für die API erstellt, die dem Benutzer angibt, wie er das Problem mithilfe der Konfigurationsdatei lösen kann.

Tutorial iTextSharp: Erstellen von PDF-Objekten

Texte

In iTextSharp werden Texte mithilfe von drei Klassen zusammengesetzt: Chunk, Phrase und Paragraph. Diese sollen in den nächsten Zeilen etwas näher beschrieben werden.

Chunk

Die Klasse Chunk befindet sich im Namespace iTextSharp.text. Chunk stellt die „kleinste“ Einheit in iTextSharp dar. Es besteht aus einem String, bei dem alle Zeichen die gleiche Formatierung (also Schriftart, -größe, -stil und -farbe) besitzen.

Chunk-Objekte besitzen keinerlei Informationen über Zeilenabstände. Das heißt, dass wenn der enthaltene String zu lang ist, wird ein Wagenrücklauf (Carriage return) ohne Zeilenvorschub ausgelöst. Das Resultat ist das, dass die gerade geschriebene Zeile überschrieben wird (ohne gelöscht zu werden). Deshalb sollten Chunk-Objekte in der Regel nicht dazu verwendet werden, direkt einem Dokument hinzugefügt zu werden. Stattdessen sollten sie in Kombination mit anderen Objekten verwendet werden.

Code-Beispiel

Document doc = new Document(PageSize.A4);
PdfWriter writer = PdfWriter.GetInstance(doc, File.Create(„Zitate.pdf“));
doc.Open();
Chunk zitatEinstein = new Chunk(„Phantasie ist wichtiger als Wissen, denn Wissen ist begrenzt.“);
Chunk zitatTwain = new Chunk(„Gib jedem Tag die Chance, der schönste deines Lebens zu werden.“);
doc.Add(zitatEinstein);
doc.Add(zitatTwain);
doc.Close();

Ausgabe im Adobe Reader

image

Phrase

Ein Phrase-Objekt (engl. Satz) besteht aus einer Aneinanderreihung von Chunk-Objekten mit einem wichtigen Extra: Wenn ein Zeilenende erkannt wird, dann wird neben den Wagenrücklauf (Carriage Return) auch ein Zeilenvorschub (New Line / Line Feed) ausgelöst, sodass sich die Chunks nicht mehr überschreiben. Der Zeilenabstand wird auch als Leading bezeichnet und kann im Konstruktor als auch in der Leading-Eigenschaft der Phrase-Klasse als float-Wert angegeben werden.

Zusätzlich ist es möglich, eine Hauptschriftart anzugeben, mit der das Phrase-Objekt formatiert wird. Somit erhalten alle enthaltenen Chunk-Objekte die gleiche Formatierung und ein einheitliches Aussehen. Aber ebenso kann man auch jedes Chunk-Objekt einzeln formatieren.

Code-Beispiel

Document doc = new Document(PageSize.A4);
PdfWriter writer = PdfWriter.GetInstance(doc, File.Create(„Zitate.pdf“));
doc.Open();
Chunk zitatEinstein = new Chunk(„Phantasie ist wichtiger als Wissen, denn Wissen ist begrenzt.“);
Chunk zitatTwain = new Chunk(„Gib jedem Tag die Chance, der schönste deines Lebens zu werden.“);
Phrase zitate = new Phrase(25);
zitate.Add(zitatEinstein);
zitate.Add(zitatTwain);
doc.Add(zitate);
doc.Close();

Ausgabe im Adobe Reader

image

Paragraph

Das Paragraph-Objekt verbindet mehrere Chunk- und Phrase-Objekte zu einem Absatz (Paragraph, engl. Absatz). Nach jedem Absatz wird automatisch ein Zeilenumbruch erstellt. Das entspricht etwa dem, dass man einem Phrase-Objekt ein Chunk.NEWLINE oder new Chunk(„n“) anhängt.

Die Paragraph-Klasse leitet von der Phrase-Klasse ab und ergänzt diese um einige Funktionen:

image

Abbildung 3: Klassenhierarchie bei Phrase und Paragraph

Der Zusammenhang zwischen Chunk, Phrase und Paragraph soll in folgender Abbildung verdeutlicht werden:

Bild1

Abbildung 4: Zusammenhang zwischen Paragraph, Phrase und Chunk

Initialisierung

Die Paragraph-Klasse besitzt mehrere Konstruktoren:

public Paragraph()
public Paragraph(float leading)
public Paragraph(Chunk chunk)
public Paragraph(float leading, Chunk chunk)
public Paragraph(string str)
public Paragraph(string str, Font font)
public Paragraph(float leading, string str)
public Paragraph(float leading, string str, Font font)
public Paragraph(Phrase phrase)

Ein Absatz kann also aus Chunks, Phrases oder String-Objekten erstellt werden. Über das Font-Objekt kann eine beliebige Schriftart für den Absatz festgelegt werden.

Abstand zwischen den Zeilen festlegen

Zusätzlich unterstützen einige Konstruktoren die Definition von Zeilenabständen (hier leading genannt). Alternativ kann über die Leading-Eigenschaft der Zeilenabstand festgelegt werden.

Abstand zwischen Absätzen festlegen

Den Abstand zwischen einzelnen Absätzen kann über die Eigenschaften SpacingBefore und SpacingAfter gesteuert werden. Dabei wird der angegebene Wert zu dem Wert addiert, der als leading definiert wurde.

Ausrichtung

Als „Besonderheit“ beim Paragraph-Objekt ist die Möglichkeit, Textausrichtungen vorzunehmen. Dazu wird die Methode SetAlignment verwendet. Als Parameter wird ein String erwartet, der als Konstante in ElementTag-Klasse festgelegt ist. Mögliche Werte sind: ALIGN_LEFT, ALIGN_CENTER, ALIGN_RIGHT, ALIGN_JUSTIFIED, ALIGN_JUSTIFIED_ALL, ALIGN_TOP, ALIGN_MIDDLE, ALIGN_BOTTOM und ALIGN_BASELINE.

Ebenso wäre es möglich, anstatt die SetAlignment-Methode zu verwenden, der Alignment-Eigenschaft einen Wert zuzuweisen. Dieser Wert muss dabei vom Typ integer sein. Auch hier gibt es wieder Konstanten, die zum Setzen der richtigen Ausrichtung verwendet werden – allerdings sind sie in der Element-Klasse definiert: ALIGN_LEFT, ALIGN_CENTER, ALIGN_RIGHT, ALIGN_JUSTIFIED, ALIGN_JUSTIFIED_ALL, ALIGN_TOP, ALIGN_MIDDLE, ALIGN_BOTTOM und ALIGN_BASELINE.

Wann man welche Methode verwendet, ist reine Geschmackssache. Intern wird SetAlignment auf Alignment gemappt.

Nachfolgend ein paar Code-Beispiele für die Textausrichtung im Absatz:

Document doc = new Document(PageSize.A4);
PdfWriter writer = PdfWriter.GetInstance(doc, File.Create(„Zitate.pdf“));
doc.Open();
Chunk zitatTwain = new Chunk(„Gib jedem Tag die Chance, der schönste deines Lebens zu werden.“);
zitatTwain.Font = FontFactory.GetFont(„Tahoma“, 24);
Paragraph paragraphTwain = new Paragraph(zitatTwain);

Setzen der Ausrichtung über SetAlignment-Methode

paragraphTwain.SetAlignment(ElementTags.ALIGN_CENTER);
doc.Add(paragraphTwain);
paragraphTwain.SetAlignment(ElementTags.ALIGN_LEFT);

Setzen der Ausrichtung über Alignment-Eigenschaft

doc.Add(paragraphTwain);
paragraphTwain.Alignment = Element.ALIGN_RIGHT;
doc.Add(paragraphTwain);
doc.Close();

Ausgabe im PDF

image

Einrückungen am Absatzanfang

Weiterhin besitzt das Paragraph-Objekt noch die Möglichkeit, Einrückungen am Anfang eines Absatzes einzufügen. Die wird mithilfe der IdentationLeft- und IdentationRight-Eigenschaft.

Tutorial iTextSharp: Writer & Meta-Daten

Das Writer-Objekt

Nachdem nun das Document-Objekt definiert wurde, wird zum Schreiben ein Writer-Objekt benötigt. Dieses entscheidet, wie das Dokument später geschrieben wird. Für jeden „Dokumenttyp“ gibt es ein Writer-Objekt: Beispielsweise wird für PDF-Dokumente ein PdfWriter benötigt, während für TeX-Dokumente ein TeXWriter instanziiert werden muss usw. Das Konzept von iTextSharp ermöglicht auch eine Dokumenttypen und –writer zu erstellen. Diese müssen nur von der abstrakten Klasse DocWriter aus dem iTextSharp.text-Namespace abgeleitet sein.

image

Abbildung 1: Klassenhierarchie DocWriter

Der Konstruktor der Writer-Klassen ist nicht öffentlich (private), sodass mittels GetInstance-Methode ein neues Objekt erzeugt werden muss. GetInstance erwartet zwei Parameter: das Document-Objekt (Das Document-Objekt) und einen Stream, in den die Ausgabe geschrieben wird.

Beispiel für das Erstellen eines neuen PDF-Writers:

Document doc = new Document(PageSize.A4.Rotate());

PdfWriter writer = PdfWriter.GetInstance(doc, File.Create(„test.pdf“));

Zum Schreiben einer PDF-Datei wird das writer-Objekt nicht mehr direkt verwendet (natürlich hält jedes Writer-Objekt einen IDocListener auf das Document-Objekt und wird somit über Änderungen im Document informiert). Stattdessen ermöglicht es weitere Features, auf die in den entsprechenden Kapiteln eingegangen wird:

  • Bearbeiten vorhandener PDF-Dokumente
  • Direktes Zeichnen von Grafiken
  • Verschlüsseln von PDF-Dokumenten
  • Viewer-Einstellungen vornehmen (Anzeige mit/ohne Menüleiste, Vollbild-Modus, Vorschaubilder anzeigen, Seiten-Skalierung etc.)
  • Dateien anhängen

PDF Meta-Daten bearbeiten

Die Meta-Daten im PDF-Dokument beinhalten allgemeine Informationen über das Dokument. Im Adobe Acrobat Reader können die Meta-Daten in den Dokumenteigenschaften (Datei à Dokumenteigenschaften / Strg + D) angesehen werden:

image

Abbildung 2: PDF-Metadaten

Rot hervorgehoben sind die jeweiligen Metadaten, die programmatisch auf dem Document-Objekt gesetzt werden können:

Eigenschaft in den Metadaten

image

Beispiel für das Setzen der Meta-Daten:

doc.AddAuthor(„Ralf Abramowitsch“);
doc.AddCreationDate();
doc.AddCreator(„Mein Tool“);
doc.AddKeywords(„iTextSharp“);
doc.AddKeywords(„Tutorial“);
doc.AddKeywords(„.NET“);
doc.AddSubject(„Thema“);
doc.AddTitle(„Titel“);
doc.AddProducer();

Tutorial iTextSharp: Einführung

iTextSharp ist eine Open Source Bibliothek zum Erstellen und Bearbeiten von PDF-Dateien. Die Java-Bibliothek iText wurde auf .NET (C#) portiert und wird synchron mit dem .NET-Pendant weiterentwickelt.

Ziel der Entwicklung war Entwicklern eine Bibliothek in die Hand zu geben, mit der sie ihre eigenen Anwendungen um PDF-Funktionalitäten erweitern konnten. In diesem Tutorial soll darum gehen, einzelne Funktionalitäten von iTextSharp anhand von Beispielen vorzustellen.

Bezugsmöglichkeiten

iText: http://www.lowagie.com/iText

iTextSharp: http://sourceforge.net/projects/itextsharp/

Tipp: Code-Dokumentation mit Doxygen erzeugen

Ich habe mich immer gefragt, warum ich denn bei IntelliSense keine Hilfe zu sehen bekomme. Ist der Code schlecht oder gar nicht dokumentiert? Nein! Der Autor hat darauf verzichtet, Kommentare im .NET-Format zu verfassen, stattdessen hat er auf das Doxygen-Format zurückgegriffen.

Somit werden keine Kommentare in die Metadaten exportiert und IntelliSense kann diese nicht auslesen. Daher ist jeder gut beraten, sich die Dokumentation kurz selbst zu erzeugen. Dazu lädt man einfach das letzte Doxygen Release von http://www.stack.nl/~dimitri/doxygen/ herunter.

Erstellen von PDF-Dokumenten

Das Document-Objekt

Das Document-Objekt stellt die Basis aller Dokument-Typen der iTextSharp-Bibliothek dar. Das Document-Objekt besitzt drei öffentliche Konstruktoren:

public Document()

public Document(Rectangle pageSize)

public Document(Rectangle pageSize, int marginLeft,int marginRight,

int marginTop, int marginBottom)

Definition der Seitengröße

Der parameterlose Konstruktor von Document verwendet als Default-Einstellung die Seitengröße A4, während die anderen beiden Konstruktoren eine Angabe der Seitengröße erwarten. Bei deren Definition kann auf die Klasse PageSize zurückgegriffen werden. PageSize besitzt öffentliche Felder, die die Auswahl von Seitengrößen vereinfacht. Mögliche Werte sind: A0 … A10, B0 … B10, HALFLETTER, LETTER, EXECUTIVE, LEGAL, NOTE, POSTCARD und einige mehr.

Definition des Seitenformats

Neben der eigentlichen Größe des PDF-Dokuments kann man auch dessen Ausrichtung, also Hochformat oder Querformat festlegen. Die Standard-Einstellung ist dabei Hochformat. Um eine Seite im Querformat anzulegen, ruft man einfach die Methode „rotate()“ auf dem von PageSize zurückgegebenen Wert auf. Ein PDF-Dokument im Querformat der Größe A4 würde wie folgt definiert werden:

Document doc = new Document(PageSize.A4.Rotate());

Definition der Seitenränder

Der dritte Konstruktor von Document bietet zudem noch die Möglichkeit, die Größe von Seitenrändern zu definieren. Die Angabe der Größe erfolgt in der Einheit „Point“ (Punkte). Die Umrechnung von „Point“ in „Zentimeter“ ist etwas umständlich, da die 72 Points genau ein Zoll darstellt. Ein Zoll wiederum besteht aus 2,54cm. Seitenränder von jeweils 2cm entsprechen also 56,693Points und werden wie folgt berechnet:

Ein PDF-Dokument der Seitengröße A4 mit jeweils 2cm Seitenrändern würde so erstellt werden:

Document doc = new Document(PageSize.A4.Rotate(), 28, 28, 28, 28);

image

Öffnen und Schließen des Documents

Document besitzt zwei wesentliche Funktionen, die zum Erstellen des PDF-Dokuments notwendig sind: Open() und Close():

Open() / IsOpen()
Um ein Dokument zu bearbeiten bzw. mit Inhalten zu befüllen, muss es geöffnet werden. Dazu ruft man die Methode Open() auf dem Document-Objekt auf. Über die Methode IsOpen() kann man abfragen, ob das Dokument bereits mit Open() geöffnet wurde.
Hinweis: Nachdem Open() aufgerufen wurde, kann man weder Meta-Daten noch PDF-Header-Informationen bearbeiten!

Close()
Sobald alle Inhalte dem Dokument hinzugefügt wurden, muss das Dokument geschlossen werden, damit es korrekt (auf die Festplatte) geschrieben werden kann. Nach dem Aufruf von Close() kann nichts mehr hinzugefügt werden.

Vortrag: iTextSharp – Eigene Programme um PDF-Funktionalität erweitern

Am Mittwoch, den 30. Juli 2008, habe ich vor der .NET-Developers Group Stuttgart einen Vortrag über die Open Source Bibliothek iTextSharp gehalten.

Den Vortrag, zusammen mit Code-Beispielen, habe ich auf meinen Webserver hochgeladen: www.abramowitsch.de/vortraege/dotnetstuttgart/itextsharp/itextsharp.zip

Viel Spaß damit 🙂

Ralf

Installation von Visual Studio 2008 Team Foundation Server unter Windows Server 2008

Am Dienstag (19.02.2008) und Mittwoch (20.02.2008) war ich zum Launch-Event von Visual Studio 2008 & Co. in Frankfurt. Dort habe ich mir bei der Vorträgen von Christian Binder und Marcus Alt Informationen zu Team Foundation Server geholt. Die Vorträge haben mich begeistert, da ich bisher ausschließlich mit Subversion und OnTime gearbeitet habe!

Als Mitbringsel von der Veranstaltung habe ich Visual Studio 2008 Standard, Windows Server 2008, sowie Visual Studio 2008 Team Foundation Server und MS SQL Server 2008 mit Heim genommen. Als ich die Verpackung aufmachte, musste ich feststellen, dass MS SQL Server 2008 noch nicht fertig ist und man mir stattdessen MS SQL Server 2005 und MS SQL Server 2008 CTP mitgegeben hat.

Voller Vorfreude habe ich dann auf meiner virtuellen Maschine Windows Server 2008 installiert. Das funktioniert echt prima! OK, dann TFS-CD rein ins Laufwerk und los geht’s! Danach folgte ich den Anweisungen des TFS Install Guide, den ich extra aus dem Internet aktualisiert herunter geladen hatte.

In dem Abschnitt „Prerequisite Installation“ stand „Before you deploy Team Foundation Server on a single server that is running Windows Server 2008, you must install IIS 7.0.“. Ok, also wieder in den Server Manager und folgende Rollen aktiviert:

  • ASP.NET
  • HTTP Redirections
  • IIS 6.0 Compatibility
  • Default options
  • Windows Authentication

Gut, danach steht in der Checklist, dass ich MS SQL Server 2005 auf Windows 2008 installieren soll. „Kein Problem“, dachte ich mir – TFS CD raus – SQL Server 2005-CD rein ins Laufwerk und Installation starten. Schon wieder eine Meldung:

Mit fehlte also zusätzlich noch Microsoft SQL Server 2005 Service Pack 2! Warum wird sowas dem Paket nicht beigelegt? Also bin ich den Hinweisen des Kompatibilitätsassistenten gefolgt und habe von der Microsoft Homepage das Service Pack 2 heruntergeladen (282MB) und nach meiner Installation von CD noch das Service Pack drauf gemacht.

Jetzt endlich sollte es losgehen! Wieder TFS-CD eingelegt und Setup gestartet. Plötzlich geht es nicht mehr weiter:

Ich habe keine Windows SharePoint Services Seite!! Woher bekomme ich diese nun? Ich erinnerte mich, dass beim Autostart der CD etwas von SharePoint Erweiterungen stand:

Also versuche ich es mal mit diesem Installer. Doch dieser bricht auch ab mit folgender Fehlermeldung:

Und abschließend stürzt das Setup dafür komplett ab:

Na toll! Woher bekomme ich nun Windows SharePoint Services 3.0? Warum ist das nicht mit auf der CD? Muss ich mir extra SharePoint anschaffen, nur um diesen TFS zu installieren? Das Handbuch half mir nicht wirklich weiter .. also musste google ran! Prompt wurde ich fündig: http://geekswithblogs.net/etiennetremblay/archive/2008/02/05/windows-2008–tfs-2008-it-works-but.aspx

Dort hatte jemand die gleichen Erfahrungen gemacht, wie ich. Allerdings bot er mir auch einen Link an, wo ich Windows SharePoint Services 3.0 (104MB) direkt von der Microsoft Homepage herunterladen konnte. Schon wenige Minuten später konnte die Installation los gehen:

[Update 01.03.2008]

Bei der Installation ist es wichtig, bei der „Erweiterten Installation“ den Servertyp von „Eigenständig“ auf „Web-Front-End Computer“ umzustellen:

Nach beendeter Installation kann die Konfiguration beginnen:

[Update 28.02.2008]

Ich führe nun die Kommandozeilen-Befehle aus, die im TFSInstall.chm stehen:

stsadm.exe -o extendvs -exclusivelyusentlm -url http://WSSServerName:Port -ownerlogin DomainUserName -owneremail „admin@localhost“ -sitetemplate sts -description „Description“

Das Ergebnis der Aktion sieht man hier:

„Die von Ihnen ausgewählte IIS-Website wird von SharePoint verwendet. Sie müssen einen anderen Port oder Hostnamen auswählen.“

Hä? Ich hab doch extra alles so befolgt, wie es in der Hilfe stand!?

Ich werde dann mal den Tipp von Peter (siehe Comments) befolgen und mein „Sharepoint 80“ löschen und dann die Kommandozeilen-Befehle neu ausführen. Leider hat das auch nix gebracht …

[Update 03.03.2008]

Nachdem mir Peter den entscheidenden Tipp gegeben hat, dass ich nicht denselben Port verwenden darf, wie meine Sharepoint-Administration (sorry 🙁 ), funktioniert alles wie es soll:

Nun schalte ich noch die Logins für den extra eingerichteten TFSSetup-User frei:

stsadm.exe -o siteowner -url http://tfs2k8:80 -secondarylogin TFSSetup

Für alle die es interessiert, wo dann die Seite mit stsadm.exe angelegt wird:

image

Dann kann’s ja mit der Installation losgehen …

[Update 04.03.2008]

Ich habe nun die Schritte bis zur Eingabe des Sharepoint Servers durchgeführt. Nun erhalte ich allerdings folgende Meldung:

image

Meine Einstellungen für Windows SharePoint Services sind die Folgenden:

image

Hier versuche ich es mal mit dem Tipp von Thomas und Peter (siehe Comments) und gebe statt http://TFS2k8:80/Sites –> http://TFS2k8:80/Websites an, da ich ebenfalls über die deutsche Version von Team Foundation Server verfüge:

image

Erst hat er rumgezickt .. aber es dann doch mit diesen Einstellungen übernommen!

Also ganz wichtig bei der deutschen Version von Team Foundation Server:

Statt http://SERVERNAME:PORT/Sites –> http://SERVERNAME:PORT/Websites

image

Und das ist die „Belohnung“ für die Mühen 🙂

image

Die Installation ist endlich abgeschlossen.

Zusammenfassung der wichtigsten Links soweit

Microsoft SQL Server 2005 Service Pack 2: http://www.microsoft.com/downloads/details.aspx?FamilyID=d07219b2-1e23-49c8-8f0c-63fa18f26d3a&DisplayLang=en

Windows SharePoint Services 3.0 with Service Pack 1: http://www.microsoft.com/downloads/details.aspx?FamilyId=EF93E453-75F1-45DF-8C6F-4565E8549C2A&displaylang=en