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();