Die Bodenblicktheorie

Kurz

Es ist die Theorie darüber, dass 30% eines menschlichen Blickfelds immer den Boden erfasst. Daher kann eine Straßenszene komplett anders aussehen, je nach dem, wie der Boden gestaltet ist. Anders gesagt: Ein Ort kann durch Umgestaltung des Bodens unglaublich verbessert werden.

Worum es geht

In dieser Theorie geht es darum, wie sehr ein Stadtbild und der Eindruck, den ein Individuum davon hat, vom Boden abhängt. Also gar nicht mal so sehr von den Häusern und deren Fassaden, sondern mehr vom Boden. Dabei ist der Boden eher so ein Ding, das mit Füßen getreten wird und auf dem so mancher auch sein Geschäft macht. Also ein eher weniger beachteter Teil. Dieser Beitrag möchte dem darauf aufmerksam machen, dass der Boden z.B. auch in der Stadtentwicklung einen ganz wesentlichen Beitrag zum Gesamteindruck beiträgt.

Theorie der zerbrochenen Fensterscheibe

Vermutlich bekannt, sonst nachlesen. Im Grunde zieht kaputt mehr Zerstörung nach sich. Daher sofort Unrat und Defekte beseitigen. So ziehen z.B. schlecht gepflegte, inhomogene und geflickte Straßen auch Hundekot und Müll magisch an.

Refaktorisieren

Heute wollen wir uns mal mit dem Thema Refaktorisieren/Refactoring beschäftigen. Wir schauen uns mal an, was es ist, und wie und ob es bei Softwareprojekten sinnvoll einzusetzen ist. Kurze Antwort: Refaktorisierungen sind ein extrem nützliches Werkzeug zur Verbesserung der Codequalität.

Definition

Die Wikipedia schreibt dazu: Refaktorisierung (auch Refactoring, Refaktorierung oder Restrukturierung) bezeichnet in der Software-Entwicklung die manuelle oder automatisierte Strukturverbesserung von Quelltexten unter Beibehaltung des beobachtbaren Programmverhaltens. Dabei sollen Lesbarkeit, Verständlichkeit, Wartbarkeit und Erweiterbarkeit verbessert werden, mit dem Ziel, den jeweiligen Aufwand für Fehleranalyse und funktionale Erweiterungen deutlich zu senken.

Martin Fowler, Autor des Buchs Refactoring, hat das Thema populär gemacht. Sehen wir uns hier seine Definition an: Refactoring is a controlled technique for improving the design of an existing code base. Its essence is applying a series of small behavior-preserving transformations, each of which „too small to be worth doing“. However the cumulative effect of each of these transformations is quite significant. By doing them in small steps you reduce the risk of introducing errors. You also avoid having the system broken while you are carrying out the restructuring – which allows you to gradually refactor a system over an extended period of time.

Hintergrund

Hinter dem Refaktorisieren steht eine mathematische Theorie. Es handelt sich um Gruppenopertionen und man kann die Beweise führen, dass gewisse Refaktorisierungsoperationen den Programmkode bedeutungsidentisch transformieren. So, als ob funktionell nichts geschehen sei. Per Induktion kann man dann daraus ableiten, dass eine Folge von Refaktorisierungsoperationen auch Bedeutungserhaltend sind. Somit ist also der Gesamte Vorgang nicht Bedeutungsändernd.

Praxis

In der Praxis jedoch wird man aber genau deshalb Refaktorisieren, weil man eine Änderung oder Erweiterung mit den bisherigen Strukturen nicht mehr leisten kann. Man benötigt also eine Abstraktion und sobald man diese eingezogen hat, wird man wechselweise Kodeänderungen und Refaktorisierungen durchführen. Somit ist die Gesamtoperation natürlich nicht Bedeutungserhaltend. Allerdings war ja auch das glatte Gegenteil das Ziel: Ein Entwickler sollte ja die Kodebasis irgendwie erweitern. Und damit das Verhalten Ändern.
Das Gute an der Sache ist eben, dass das Refaktorisieren zunächst ein Schritt ist, der in der Theorie keine Bedeutungsänderung bringt.

Beispiele

An dieser Stelle sollten wir den Blick mal auf einige Beispiele von Refaktorisierungsoperationen werfen, um ein wenig mehr Gefühl dafür zu bekommen.

Hier einige typische Refaktorisierungen:

Umbenennen

Das einfachste ist sicherlich das Umbenennen. Natürlich stellt sich die Frage was. Man kann ja eine lokale Variable, eine Elementvariable, Klasse, Templateparameter, Elementfunktion, Namespace u.v.a.m umbenennen. Die Implikationen sind jeweils ähnlich; können aber weite Kreise ziehen. Inklusive eventueller Dateiumbenennungen. So lange keine Konflikte entstehen, sollte evident sein, dass dies eine harmlose Operation ist:

private static void Gelder(int zahl)
{
    int doppelt = zahl + zahl;
    Console.WriteLine($"{zahl} Heller verdoppelt sind {doppelt} Heller");
}

public static void Main(string[] args)
{
    Gelder(zahl: 4);
}

Durch Umbenennung des Parameters zahl in nummer werden alle Stellen kohärent angepasst.

private static void Gelder(int nummer)
{
    int doppelt = nummer + nummer;
    Console.WriteLine($"{nummer} Heller verdoppelt sind {doppelt} Heller");
}

public static void Main(string[] args)
{
    Gelder(nummer: 4);
}

Extrahieren und Entrollen einer Variable

Hierbei handelt es sich um zwei gegenteilige Operationen. Extrahieren bedeutet, dass aus einem Ausdruck eine neue Variable entsteht. Entrollen ist das Gegenteil. Alle Vorkommen einer Variable werden durch den Ausdruck ersetzt. Sehen wir folgendes Beispiel an:

private static (double umfang, double fläche) Kreis(double radius)
{
    double rp = radius * Math.PI;
    double umfang = 2 * rp;
    double fläche = radius * rp;
    return (umfang, fläche);
}

Der Teilausdruck radius * Math.PI wurde hier ein eine Variable rp gepackt. Das könnte vielleicht eine Rechnung ersparen, verringert aber eher die Lesbarkeit. Daher konnte die Variable rp entrollt werden und an ihrer Stelle steht nun der Ausdruck. Dieser Umbau kann durch Extraktion umgekehrt werden.

private static (double umfang, double fläche) Kreis(double radius)
{
    double umfang = 2 * radius * Math.PI;
    double fläche = radius * radius * Math.PI;
    return (umfang, fläche);
}

Skalar zu Tupel-Transformation

Diese Refaktorisierungsoperation habe ich mir wohl selbst ausgedacht, da ich sie so noch nie gesehen habe. Es handelt sich um das umkehrbare aufblasen einer skalaren Variable in eine Kompositvariable. Also typischerweise in eine Liste, Feld, Tupel oder Struktur/Objekt. Der Anwendungsfall ist in typischen Applikationen recht häufig. Werfen wir einen Blick auf diese sehr vereinfachte Businessanwendung, die eine Person mit ihrer Adresse ausgibt:

private class Anschrift
{
    public string Straße;
    public int Nr;
}
private struct Person
{
    public string Name;
    public Anschrift Adresse;
}
private static void WriteAdresse(Person p)
{
    Console.WriteLine("Name :" + p.Name);
    Console.WriteLine($"Wohnhaft in {p.Adresse.Straße} {p.Adresse.Nr}");
}

Nun stellt sich heraus, dass eine Person mehrere Adressen haben kann. Also aus dem Skalar Adresse wird eine Liste von Adressen. Die Funktion bleibt zunächst dieselbe. Allerdings wird der Zugriff auf die Adresse nun mit einem Indexzugriff verziert. Die Funktion bleibt dieselbe:

private class Anschrift
{
    public string Straße;
    public int Nr;
}
private struct Person
{
    public string Name;
    public List<Anschrift> Adresse;
}
private static void WriteAdresse(Person p)
{
    Console.WriteLine("Name :" + p.Name);
    Console.WriteLine($"Wohnhaft in {p.Adresse[0].Straße} {p.Adresse[0].Nr}");
}

In einem nächsten Schritt kann man in WriteAdresse eine for-Schleife einbauen, die alle Adressen ausgibt.

Klasse extrahieren

Bei dieser Operation werden Methoden mit all ihren Abhängigkeiten aus einer Klasse in eine neue Klasse extrahiert. Diese Refaktorisierung hilft insbesonder beim S in SOLID, der „Single Responsibility„. In diesem Beispiel sehen wir, wie die beiden Methoden völlig unabhängig voneinander in derselben Klasse stehen. Über die sinnhaftigkeit ist hinwegzusehen:

public class RechenKnecht
{
    private readonly int _num;
    private readonly string _name;

    public RechenKnecht(int num, string name)
    {
        _num = num;
        _name = name;
    }

    public int Rechne()
    {
        return _num * 2;
    }

    public string Gruß()
    {
        return "Edler von " + _name;
    }
}

Hier wurde die Methode Gruß() in die Klasse Ansprache extrahiert. Man hätte es natürlich auch umgekehrt machen können:

public class RechenKnecht
{
    private readonly int _num;

    public RechenKnecht(int num)
    {
        _num = num;
    }

    public int Rechne()
    {
        return(_num * 2);
    }
}
public class Ritter
{
    private readonly string _name;

    public Ritter(string name)
    {
        _name = name;
    }

    public string Gruß()
    {
        return "Edler von " + _name;
    }
}

Auch diese Opertion ist umkehrbar…. bis am Ende alles Methoden und der Gesamtzustand der Anwendung in einer Klasse ist. Das ist aber natürlich unwartbar.

Das war natürlich ein triviales Beispiel. Wäre aber der Gruß nun zusätzlich mit der berechneten Zahl ausgestattet gewesen, dann geht es so nicht. Aber man kann dann mit Komposition arbeiten. Stellen wir uns also diese Gruß()-Methode vor:

    public string Gruß()
    {
        int x = Rechne();
        return $"{x}ter Edler von {_name}";
    }

In diesem Fall kann man mit folgender Refaktorisierung arbeiten. Hierbei wurde RechenKnecht auf seine Essenz redzuiert und dem Ritter als Vehikel gegeben (Komposition). Es sind zwei Klassen entstanden:

public class RechenKnecht
{
    private readonly int _num;

    public RechenKnecht(int num)
    {
        _num = num;
    }

    public int Rechne()
    {
        return(_num * 2);
    }
}
public class Ritter
{
    private readonly string _name;
    private readonly RechenKnecht _knecht;

    public Ritter(string name, int num)
    {
        _name = name;
        _knecht = new RechenKnecht(num);

    }

    public string Gruß()
    {
        int x = _knecht.Rechne();
        return $"{x}ter Edler von {_name}";
    }
}

Elemente rauf- und runterziehen

Eine Refaktorisierungsoperation möchte ich noch zeigen, ehe ich zum Aha übergehe, eine objektorientierte: Elementfunktionen in der Erbhierarchie raufschieben oder runterziehen. Auch hier besteht wieder (muss ja) eine Umkehrbarkeit und Bedeutungsgleiche in beiden Richtungen. Sehen wir uns dieses Beispiel an. Dort wird eine Elementfunktion aus der Vaterklasse aufgerufen. Außerdem ist die Methode in der Schnittstelle deklariert. Da aber niemand sonst darauf zugreift, kann der Kode kürzer und enger gefasst werden:

public interface IRechner
{
    int Quadrat(int num);
    void Rechne(int num);
}

public class BasisRechner : IRechner
{
    public virtual int Quadrat(int num)
    {
        return num + num;
    }

    public virtual void Rechne(int num)
    {
        Console.WriteLine(num);
    }
}

public class QuadratRechner : BasisRechner
{
    public override void Rechne(int num)
    {
        Console.WriteLine(Quadrat(num));
    }
}

Da die Methode Quadrat() in BasisRechner überhaupt nicht verwendet wird, kann man sie runterschieben in QuadratRechner. Man kann sie sogar aus der Schnittstelle entfernen (soweit nie benutzt) und dabei die Funktionalität beibehalten:

public interface IRechner
{
    void Rechne(int num);
}

public class BasisRechner : IRechner
{
    public virtual void Rechne(int num)
    {
        Console.WriteLine(num);
    }
}

public class QuadratRechner : BasisRechner
{
    public override void Rechne(int num)
    {
        Console.WriteLine(Quadrat(num));
    }

    public virtual int Quadrat(int num)
    {
        return num + num;
    }
}

Es gibt natürlich noch viele weitere Refaktorisierungsoperationen. Dies sollte nur eine kleine Auswahl sein, um einen Eindruck zu vermitteln.

Anwendung

Das Ganze lässt sich natürlich wunderbar automatisieren und in Werkzeuge gießen. So sind mit z.B. ReSharper, Eclipse, Visual Studio oder IntelliJ wunderbare Refaktorisierungswerkzeuge entstanden, die das Leben eines Entwicklers wirklich erleichtern. Vor allem aber arbeiten sie als Automaten viel konistenter und korrekter als es ein Entwickler langfristig könnte….

Insofern rate ich auch grundsätzlich dazu, immer Werkzeuge für solcherlei Vorhaben zu benutzen und sich dabei unbedingt vom Rechner unterstützen lassen. Ausnahmen bestätigen die Regel…

Mit diesem Wissen und diesen Werkzeugen im Gepäck kann man natürlich auch die Qualität des Quelltexts verbessern. Ich unterscheide hier mal alltägliche und gezielte Anwendung.

Alltägliche Anwendung

In der alltäglichen Anwendung nutzt ein Entwickler die Werkzeuge während des Kodeschreibens. Erstens, um architektonische Anpassungen zu machen, um die neue Funktion einzubauen. Und zweitens, um schlicht schneller zu sein. Stellt er fest, dass ein Dreizeiler öfter mehrfach im Kode steht, kann er ‚mal eben‘ eine Funktion extrahieren. Das Werkzeug macht den Rest. Im Anschluss kann er sie noch schnell umbenennen. Oder: Im Gefecht der Entstehung neuer Kodezeilen hat der Entwickler zunächst einige temporäre Variablen angelegt. Im Weiteren Verlauf stellt er fest, dass eine davon nur an einer Stelle verwendet wird. Ergo kann er sie automatisch Ausrollen lassen und direkt durch die rechte Seite der Variable austauschen. Ein weiteres gutes Beispiel ist ein fehlender/überflüssiger Parameter an einer Methode. Werkzeug anstarten und alle Aufrufe werden angepasst. Z.B. mit einem definierbaren Standardwert.

Refaktorisieren und die Werkzeuge stellen einfach einen Produktivitätsfaktor im Entwickleralltag dar und sind heute nicht mehr wegzudenken.

Gezielte Anwendung

Unter gezielter Anwendung verstehe ich dedizierte Sitzungen, in denen Refaktorisert wird. Entweder alleine oder mehrere Leute. Also z.B. ein Codereview, Paarprogrammierung oder doch wieder der einzelne Entwickler. Es geht dabei darum, dass ein Kodeabschnitt identifiziert wurde, der architektonisch oder in seiner Art so nicht mehr tragbar ist. Man macht sich also alleine oder als Gruppe Gedanken darüber, wie man umbauen sollte und tut dies dann auch. In aller Regel kommt etwas Besseres dabei heraus. Dabei sollte man sich nicht gleich ins Bochshorn jagen lassen, wenn sich zunächst ein Tal der Tränen auftut. Mit etwas gezieltem Handeln und unbeirrbarkeit bekommt man das regelmäßig wieder hin und freut sich nacher über ein besseres Ergebnis.

Probleme und Gefahren

Die Realität ist natürlich wieder viel komplexer und gemeiner als die graue Theorie und mein Geschwafel. Gerade in der imperativen objektorientierten Programmierung (OOP) haben Objekte regelmäßig Zustand und damit werden Instanzen, Referenzen und Vielfachheiten sowie die Reihenfolge interessant. Tatsächlich funktioniert die Theorie hier auch in der Praxis, aber man refaktorisiert ja gerade an Programmstücken, die es besonders nötig haben.

Die Gottklasse entmachten

Eine häufige Ursache für Wust ist, wenn eine Klasse mehrere Aufgaben erledigt = mehrere Klassen in sich vereint. Wenn das extrem wird, nennt man das Gottklasse. Quasi immer ist das die Ursache für nicht auflösbare zirkuläre Referenzen. Daher sollte man genau danach ausschau halten und die Operation Klasse Extrahieren verwenden. Lieber einmal mehr auf Verdacht anwenden. Wenn sich eine Aufteilung von Methoden und Elementvariablen finden lässt, dann war es der Fall und es sollte dann in mehrere Klassen Aufgespalten werden.

Dies ist eine der erfolgversprechendsten Refaktorisierungen. Im Nachgang ist es häufig so, dass eine der beiden extrahierten Klassen eine Vielfachheit bekommt (Skalar zu Tupel-Transformation).

Asynchron und Multithreading

Hier wird es spannend. Grundsätzlich sind auch hier alle Refaktorisierungen sinnvoll verwendbar und machen wenig Probleme. Die Probleme liegen allerdings im Detail. So kann es passieren, dass sich durch die neue Architektur die Lebenszyklen von Objekten verändern und dann bei Async-Programmierung Zugriffsfehler passieren. Oder Zustände sich früher/später als vor der Refaktorisierung verändern. Wie gesagt alles kann und es kommt immer auf die tatsächliche Implementierung an. Allgemein lässt sich aber sagen, dass man mit einer Architektur gut Async+Multithreading besteht, wenn sie „Single Responsibility“, „Interface Segregation“ und „Immutable Pattern“ beherzigt. Doch in diese Richtung kann und sollte man „hinrefaktorisieren“.

Resumé

Damit schließe ich diesen Artikel mal ab und resumiere, dass Refaktorisierung eine sehr nützliche Sache ist und sich fast immer lohnt. Insofern sollte ein jeder Projektleiter in der Software regelmäßig anberaumte Refaktorisierungs-Sitzungen veranstalten, in denen der Code an problematischen Stellen verbessert wird. Nebenbei helfen solche „öffentlichen Reviews“ auch frischen Etnwicklern etwas Neues zu lernen.

Rechtsanwaltskanzleien – wenn die wie Frisörsalons heißen

Was wäre, wen Rechtsanwälte genauso lustige oder fantasievolle Namen hätten, wie Frisörsalons

  • Kanzlei Recht & Beuger
  • Kanzlei Paragraph & Reiter
  • Kanzlei Folgen & Schätzle
  • Kanzlei Rechthaber
  • Kanzlei Rechtsverrdreher
  • Kanzlei Berat + Schlafgut
  • Partnerschaft Hauer & Stecher
  • Kanzlei Pechsträhne
  • Kanzlei Glücksträhne
  • Syndikat Hauser, Fried & Bruch
  • Law & Ordner
  • Kanzlei Letz & Akten
  • Kanzlei Haber & Guth
  • Die Rechtsverdreher
  •  
Karl-Marx-Monument Chemnitz

Geringes Gehalt macht abhängig und fleißig

Tja, vermutlich zitiere ich, nicht gewahr Karl Marx‘ Das Kapital, gerade ein Kapitel aus demselben. Aber seit Euch gewiss, dies habe ich mir soeben selbst ausgedacht / festgestellt

These

Meine These ist also die, dass Beschäftigte, die geringe Gehälter erhalten mehr arbeiten und weniger Alternativen haben.

Hohes Gehalt

Nehmen wir als Startbeispiel einen Angestellten mit einem hohen Gehalt. Dieser kann trotz seines Lebensstils 50% seines Nettoeinkommens auf die Hohe Kante legen. Damit müsste er theoretisch nur ein halbes Jahr arbeiten und kann das andere halbe Jahr machen was er will. Zum Beispiel kann er sich auch nach einem noch höher dotierten Job umsehen oder eine Firma Gründen und richtig Millionär werden. Oder auch nur hübsch 50% seines Einkommens in Aktien investieren und selber Kapitalsit werden und von den Dividenden leben. Er wird also unabhängiger vom Arbeitskapitalismus.

Geringes Gehalt

Sehen wir uns dagegen einen Arbeiter mit geringem Gehalt an. Er muss jeden Monat malochen und jede Überstunde Mitnehmen, um sich sein Leben leisten zu können. Er gibt rund 85% seines Nettoeinkommens jeden Monat aus. Es bleibt kaum was zum Sparen und für schlechte Zeiten. Wenn mal was passiert (Thema Waschmaschine kaputt), sind die Ersparnisse im Nu wieder weg. Dieser Arbeiter wird also, gerade ob seines eher höheren Arbeitspensums, kaum Zeit haben, sich nach Jobalternativenumzusehen. Eventuell müsste er auch einen Monat Gehalt überbrücken – unmöglich. Also ist er verdammt dazu weiterhin für seinen gering zahlenden Arbeitgeber zu arbeiten. In Aktien zu investieren, geschweige denn vom Kapital zu leben und somit teil des Kapitalismuszu werden ist nur in homöopathischenDosen möglich. Faktisch also nicht.

Dieser Arbeiter wird also versuchen möglichst noch Überstunden zu machen und alles zu tun, damit er seinen Job nicht verliert. Sich unabhängig machen durch Selbständigkeit wird ihm verunmöglicht, da er keine substantiellen Ersparnisse hat.

Resumé

Das ist, wie Kapitalismus und Arbeit als Ressource funktioniert. Man hält sich den kleinen Arbeiter kurz, damit er froh ist, überhaupt irgendwas zu haben. Wenn es dann noch günstiges Schweinefleisch zu kaufen gibt (qua Politik) und Fußball, ist er sogar ein bisschen glücklich.

Automatisierte Builds in Azure DevOps/VSTS

Ein essentieller Teil einer erfolgreichen Softwareenwicklung in einem Unternehmen ist Continuous Integration, ein ständig verfügbarer kontinuierlicher Build. Und daneben gehört natürlich Continuus Deployment hinein. So werden fertige Builds auch gleich auf Herz und Nieren geprüft.

In diesem Artikel geht es darum, wie man aus einem .Net 4.7-Projekt mit mehreren Setups und exe-Dateien (GUI und CUI) einen continuus Build hinbekommt. Dabei sollen die Versionsnummern immer stetig hochgezählt werden und sowohl in jeder Assembly als auch in jedem Setup landen. Zusätzlich gibt es einige GUI-Komponenten, die das unsägliche .Net-licensing verwenden. Auch dafür gibt es eine Lösung.

Der Build wird eingerichtet auf Azure DevOps. Modernere Versionen sollten Ähnlich oder einfacher einzurichten sein.

Ziel

Folgende Probleme sollen adressiert und gelöst werden:

  • .Net Licensing von GUI-Komponenten (z.b. PerpetuumSoft)
  • Versionsnummern automatisch vergeben
  • Basis der Versionsnummer konfigurierbar (beim Build)
  • Versionsnummer in jeder Assembly (Asm. Ver + File Ver.)
  • Versionsnummer in jedem MSI, „Drüberinstallieren“ soll gehen.

Lizensierung

Mit .Net Licensing hat Microsoft ehemals ein Standardverfahren etabliert, mit dem Komponenten (v.a. Windows Forms) lizensiert werden können. Aufgrund des Alters und seiner Art, ein eher unsägliches Ding. Aber man kommt damit klar.

Die primäre Idee von .Net Licensing ist es, für GUI-Komponenten Entwicklerlizenzen während der Entwicklung durchzusetzen. Das sieht dann etwa so aus: Der Entwickler bindet eine GUI-Komponente ein und bei jeder Verwendung prüft diese, ob auf dem Entwicklerrechner eine Lizenz vorhanden ist. Ist das nicht der Fall, kann etwa ein Nag-Screen aufpoppen. Dazu gehört noch der Lizenzcompiler LC.Exe, der aus *.licx-Dateien *.licenses-Dateien baut. Das erledigt jeweils die Komponente, die auf ihre Weise checkt, ob sie auf diesem Entwicklersystem lizenziert ist. Deshalb steht auch in der *.licx-Datei eine Liste von Komponenten, die der LC.EXE aufrufen soll (per Reflection). Im Ergebnis entsteht eine *.licenses-Datei, die als Eingebettete Ressource in die Assembly eincompiliert wird. Danach ist die Komponente dauerhaft auch auf Kundensystemen lizensiert.

Das Problem ist nun: So lange alles nur auf Entwicklermaschinen lief, ging alles gut. Der CI-Build läuft aber auf einer frisch instantiierten VM in der Cloud und diese hat keine Entwicklerlizenz installiert. Ergo kann es passieren, dass ein Nag-Screen aufgeht und keiner ihn kann klicken. Der Build wird nie fertig.

Info dazu auf Stackoverflow

Lösung: Man macht einmal einen ordentlichen Build auf einem Entwicklersystem und sammelt dort die *.licenses-Dateien ein (Bin-VZ). Dann setzt man den Dateityp in der SLN von *.licx auf „None„. Sodann checkt man die *.licenses Dateien pro Assembly ein und bindet sie als „Embedded Resource“ zusätzlich in die SLN ein.

Ab da läuft der Build ohne Nag-Screen durch. Nur beim Aktualisieren der Komponenten muss man diesen Vorgang wiederholen. Man macht somit LC.EXE arbeitslos und verhindert das Aufrufen des Lizensierungs-Codes.

Versionierung

Kommen wir zum Thema Versionsnummern. Die wichtigste Zutat für einen automatiserten Build sind korrekte und konfigurierbare Versionsnummern. Nur so kann man feststellen, ob ein Artefakt neuer oder älter ist und zu welchem Versionsstand er gehört. .Net bringt uns 3 verschiedene Versionsnummern, die je Assembly gesetzt werden können. Eine Erklärung dazu und zu „Semantischer Versionierung“ findet man hier:

Umsetzung

Um eine automatische Versionierung hinzubekommen braucht man zwei Zutaten:

  • Eine Pipeline-Variable für die Assemblyversion
  • Ein Skript, um die Versionsvariable zu verrechnen
  • Ein Build-Target für MSBUILD
Die Variable BuildVersionOfAssembly wird angelegt mit dem aktuellen Standardwert

Der Plan ist es, die Versionierung so zu machen, wie es in semantic versioning empfohlen wird. Also die verschiedenen Typen von Assembly-Versionsattributen korrekt zu setzen. Sprich: AssemblyVersion und AssemblyInformationalVersion ist die ‚fixe‘ Version. Darüber referenzieren und finden sich die abhängigen Assemblies. Die letzte Stelle ist leer. Derweil ist AssembyFileVersion quasi dieselbe Nummer aber mit gesetzter Build-Nummer, die irgendwie ausgerechnet wird.

Konkret kommt über die Build-Pipeline eine Variable AssemblyVersion hinein in Form einer 3-Stelligen Version. Z.B. „11.22.33“

Dies resultiert in:
AssemblyVersion = „11.22.33“
AssemblyInformationalVersion = „11.22.33“
AssembyFileVersion = „11.22.33.1928“
Das Versions-Rechen-Skript setzt die neue Variable BuildVersionOfAssembly auf den Wert „11.22.33.1928“ und exportiert sie als DevOps-Variable auf die Konsole. Die Möglichkeiten dabei sind grenzenlos. Üblicherweise sollte man die BuildID nutzen, welche eine stetig steigene Nummer je getriggerten Build im CI-System ist. Problem: Die Zahl wird irgendwann größer als die erlaubten 16-bit an dieser Stelle. Lösung: Man macht Modulo 2^16 und hofft, dass dies nur alle 65k Builds zu Problemen führt und inkrementiert dann schnell die Patch-Version (Stelle 3). Wahlweise kann man auch die oberen Bits auf Patch-Version geben; dann aber nur zwei Versionsstellen vorgeben. (alles hier nicht gemacht).

Skript

Version Script (PowerShell inline):

# This script computes the $(BuildVersionOfAssembly) variable off of the User set $(VersionOfAssembly) build var.
# adds the buid pipeline ID (ordered number) as the last figure of the version

# VersionOfAssembly is expected to be "x.y.z" BuildVersionOfAssembly becomes "x.y.z.buildID"
Write-Host  "Looking for VersionOfAssembly. Found : $Env:VersionOfAssembly"

if( -not ("$Env:VersionOfAssembly" -match "^\d+\.\d+\.\d+$" ))
{
   Write-Host  "##vso[task.LogIssue type=error;]Error: $Env:VersionOfAssembly does not match X.Y.Z format."
   exit 1
}

$BuildVersionOfAssembly = $Env:VersionOfAssembly + '.' + $Env:Build_BuildId
Write-Host  "BuildVersionOfAssembly= $BuildVersionOfAssembly"

#To set a variable in DevOps pipeline, use Write-Host
Write-Host "##vso[task.setvariable variable=BuildVersionOfAssembly]$BuildVersionOfAssembly"
Version Script in DevOps – Variablen Sektionen leer lassen

Version in Assemblies schreiben

Versionen sind als Varialben da, doch wie bekommen wir sie als Assembly-Attribute in die Assemblies hinein. MSBUILD hat das etwas unbekannte Merkmal ‚directory target‘. Dieses sind Kompile-Schritte, die MSBUILD pro Projekt durchführt. Setzt man sie richtig ein, werden diese Aufgaben für jedes Projekt im selben oder tieferen Verzeichnis ausgeführt. Siehe .

Das Skript muss also irgendwo ‚oben‘ im Projekt angelegt werden und wir nennen es ‚Directory.Build.targets‘. Innerhalb des Skripts kann sogar inline-C# verwendet werden. Dabei nutzen wir Reguläre Ausdrücke um die AssemblyInfo.cs-Dateien zu patchen (alle 3 Versionsattribute). Dabei wird aber nicht die eigentliche Datei gepatcht, sondern eine Kopie. Die Originale (im VCS eingecheckt) werden als ‚targets‘ ausgeschlossen und dafür die gepatchten hinzugefügt. Das ist besser als checkout+checkin auf Build- und Entwicklermaschinen. Denn diese Targets werden ja immer mit ausgeführt. Wenn aber BuildVersionOfAssembly leer ist, macht dieses ‚Target‘ nichts. Das Ganze sollte mit *.vb genauso funktionieren.

Script: \Directory.Build.targets

<Project DefaultTargets="Build" InitialTargets="UpdateAssemblyVersion" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!-- from:
    http://www.lionhack.com/2014/02/13/msbuild-override-assembly-version/
    Creates modified version of AssemblyInfo.cs, replaces
    [AssemblyVersion] attribute with the one specifying actual build version
    (from MSBuild properties), and includes that file instead of the
    original AssemblyInfo.cs in the compilation.

    Works with both, .cs and .vb version of the AssemblyInfo file, meaning
    it supports C# and VB.Net projects simultaneously.

    see:
    Global .NET Versioning Strategy – AssemblyInformationalVersion
    or https://intovsts.net/2015/08/24/tfs-build-2015-and-versioning/ for
    further information

    Note: C++ projects have a CommonCpp.targets
-->

  <Target Name="UpdateAssemblyVersion" BeforeTargets="Compile"
       Condition="'$(VersionOfAssembly)' != '' or '$(BuildVersionOfAssembly)' != ''">
    <!-- Find AssemblyInfo.cs or AssemblyInfo.vb in the "Compile"
        Items. Remove it from "Compile" Items because we will use a modified
        version instead. -->
    <ItemGroup>
      <OriginalAssemblyInfo Include="@(Compile)" Condition="%(Filename) == 'AssemblyInfo' And (%(Extension) == '.vb' Or %(Extension) == '.cs')" />
      <Compile Remove="**/AssemblyInfo.vb" />
      <Compile Remove="**/AssemblyInfo.cs" />
    </ItemGroup>
    <!--  Copy the original AssemblyInfo.cs/.vb to obj\ folder, i.e.
          $(IntermediateOutputPath). The copied filepath is saved into
          @(ModifiedAssemblyInfo) Item. -->
    <Copy SourceFiles="@(OriginalAssemblyInfo)"
          DestinationFiles="@(OriginalAssemblyInfo->'$(IntermediateOutputPath)%(Identity)_patched')">
      <Output TaskParameter="DestinationFiles" ItemName="ModifiedAssemblyInfo"/>
    </Copy>

    <!-- DON'T Use VersionAssembly if InfoVersionAssembly is empty -->
    <!-- <PropertyGroup> -->
    <!-- <InfoVersionAssembly Condition="'$(BuildVersionOfAssembly)'== ''">$(VersionOfAssembly)</InfoVersionAssembly> -->
    <!-- </PropertyGroup> -->

    <Message  Text="-------------------------------------------------------------------------------" Importance="high" />
    <Message Text="Setting AssemblyVersionAttribute to $(VersionOfAssembly) " Importance="high" />
    <Message Text="Setting AssemblyFileVersionAttribute to $(BuildVersionOfAssembly) " Importance="high" />
    <Message Text="Setting InfoVersionAssemblyAttribute to $(VersionOfAssembly) " Importance="high" />
    <Message Text="Temp file is %(ModifiedAssemblyInfo.FullPath) " Importance="high" />
    <Message Text="--------------------------------------------------------------------------------" Importance="high" />

    <!-- Replace the version bit (in AssemblyVersion and
        AssemblyFileVersion attributes) using regular expression. Use the
        defined property: $(VersionOfAssembly). -->

    <!-- TODO: For Relseases, AssemblyVersion should be set to InfoVersionAssembly -->
    <Message Text="Setting AssemblyVersion to $(VersionOfAssembly)" />
    <RegexUpdateFile Files="@(ModifiedAssemblyInfo)" 
                     Condition="'$(VersionOfAssembly)' != ''"
                     Regex="AssemblyVersion(Attribute)?\("(\d+)\.(\d+)\..*"\)"
                     ReplacementText="AssemblyVersion("$(VersionOfAssembly)")" 
                     />
    <Message Text="Setting AssemblyFileVersion to $(BuildVersionOfAssembly)" />
    <RegexUpdateFile Files="@(ModifiedAssemblyInfo)"
                     Condition="'$(VersionBuildVersionOfAssemblyOfAssembly)' != ''"
                     Regex="AssemblyFileVersion(Attribute)?\("(\d+)\.(\d+)\..*"\)"
                     ReplacementText="AssemblyFileVersion("$(BuildVersionOfAssembly)")"
                     />
    <Message Text="Setting InfoVersionAssembly to $(VersionOfAssembly)" />
    <RegexUpdateFile Files="@(ModifiedAssemblyInfo)"
                     Condition="'$(VersionOfAssembly)' != ''"
                     Regex="AssemblyInformationalVersion(Attribute)?\("(\d+)\.(\d+)\..*"\)"
                     ReplacementText="AssemblyInformationalVersion("$(VersionOfAssembly)")"
                     />
    <!-- Include the modified AssemblyInfo.cs/.vb file in "Compile" items (instead of the original). -->
    <ItemGroup>
      <Compile Include="@(ModifiedAssemblyInfo)" />
    </ItemGroup>
  </Target>

  <UsingTask TaskName="RegexUpdateFile" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll">
    <ParameterGroup>
      <Files ParameterType="Microsoft.Build.Framework.ITaskItem[]" Required="true" />
      <Regex ParameterType="System.String" Required="true" />
      <ReplacementText ParameterType="System.String" Required="true" />
    </ParameterGroup>
    <Task>
      <Reference Include="System.Core" />
      <Using Namespace="System" />
      <Using Namespace="System.IO" />
      <Using Namespace="System.Text.RegularExpressions" />
      <Using Namespace="Microsoft.Build.Framework" />
      <Using Namespace="Microsoft.Build.Utilities" />
      <Code Type="Fragment" Language="cs">
        <![CDATA[
try {
var rx = new
System.Text.RegularExpressions.Regex(this.Regex);
for (int i = 0; i < Files.Length; ++i)
{
var path = Files[i].GetMetadata("FullPath");
if (!File.Exists(path)) continue;

var txt = File.ReadAllText(path);
txt = "// <auto-generated />\r\n" +
rx.Replace(txt, this.ReplacementText);
File.WriteAllText(path, txt);
}
return true;
}
catch (Exception ex) {
Log.LogErrorFromException(ex);
return false;
}
]]>
      </Code>
    </Task>
  </UsingTask>

  <!-- ConsoleToMsBuild="True" IgnoreStandardErrorWarningFormat="true" IgnoreExitCode="True" -->
  <!-- MsBuild is probably the only build tool in the world that can't copy dependencies without help.-->
  <!-- Therefore, I can't recommend msbuild for professional use. -->
  <!-- Add this lines to your project file:
  <PropertyGroup
  Condition="Exists('$(SolutionDir)CommonSettings.targets')">
  <BuildDependsOn>
  $(BuildDependsOn);
  CopyLocalAgain;
  </BuildDependsOn>
  </PropertyGroup>
  -->
  <Target Name="CopyLocalAgain">
    <!-- Bug with incremental clean should be fixed. -->
    <!-- <CreateItem Include="%(Reference.HintPath)" -->
    <!-- Condition="'%(Reference.Private)'=='true'"> -->
    <!-- ItemName defines the name of the item list. Who came up with this syntax? -->
    <!-- <Output TaskParameter="Include" -->
    <!-- ItemName="_CopyLocalReferencesAgain"/> -->
    <!-- </CreateItem> -->
    <!-- <Message Text="Copy CopyLocal references again: @(_CopyLocalReferencesAgain)" /> -->
    <!-- <Copy SourceFiles="@(_CopyLocalReferencesAgain)" -->
    <!-- DestinationFolder="$(OutputPath)" SkipUnchangedFiles="true"> -->
    <!-- </Copy> -->
  </Target>
</Project>

MSI und Bootstrapper Setups

Die MSI Setups (WIX) und WIX-Bootstrapper-Setups funktionieren ein wenig anders um ihre Versionsnummer eingeimpft zu bekommen. Da es aber MSBUILD-Projekte sind, kann man ähnlich vorgehen. Quelle der Inspiration: Dieser Stackoverflow Artiel.

  1. In der Projektdatei *.wxiproj muss dieser Teil relativ an den Anfang. Hier wird die Umgebungsvariable als ‚Version‘ genommen. Bei fehlender Variable gibt es einen Fallback. Auf diese Weise gibt es immer eine gesetzte Version. Ansonsten scheitert der WIX-Build.
    <!-- define version constant if CI-Build not running  use a default -->
    <Version Condition=" '$(BuildVersionOfAssembly)' == '' ">5.99.99</Version>
    <Version Condition=" '$(BuildVersionOfAssembly)' != '' ">$(BuildVersionOfAssembly)</Version>
    <DefineConstants>Version=$(Version)</DefineConstants>
  1. In die Dateien *.wxs nutzt man die soeben gesetzte Variable (Verfpgbar als $(ver.Version)).

Beispielausschnitt einer *.wxs:

  <Product Id="CDE4C223-7A78-45FF-A984-675F414BD516" Name="!(loc.PRODUCT)" Language="1033" Version="$(var.Version)" Manufacturer="!(loc.MANUFACTURER)" UpgradeCode="cae20b70-d924-4b23-b90e-9ea1b5f5026b">
    <Package InstallerVersion="200" Manufacturer="!(loc.MANUFACTURER)" Compressed="yes" Description="!(loc.PRODUCT) $(var.Version)" />
    <Property Id="PREVIOUSVERSIONSINSTALLED" Secure="yes" />
    <Property Id="TAKEBACKUP" Secure="yes" />
    <Upgrade Id="cae20b70-d924-4b23-b90e-9ea1b5f5026b">
      <UpgradeVersion Minimum="$(var.Version)" IncludeMinimum="no" OnlyDetect="yes" Language="1033" Property="NEWERPRODUCTFOUND" />
      <UpgradeVersion Minimum="0.79.0" IncludeMinimum="yes" IncludeMaximum="no" Maximum="$(var.Version)" Language="1033" Property="UPGRADEFOUND" />
    </Upgrade>

Release

Zu einer Build-Pipeline gehört immer auch eine Release-Pipeline. Die soll aber nicht das Thema dieses Betrags werden. Hier ging es vornehmlich um das korrekte Errechnen und setzen von Assembly-Versionen und MSI-Versionen.

Als Bemerkunt oder Tip sei hier nur gesagt: Eine Build-Pipeline erzeugt Artefakte (irgendwo in Azure in einem Storage). Diese kann man im DevOps zwar herunterladen und damit arbeiten. Aber die verschwinden irgendwann. Daher benötigt man eine Release-Pipeline. Damit macht man eigentlich eher ‚Deployments‘ und Tests von Software. Man kann aber auch einfach eine leere Pipeline anlegen und einfach nur einen Copy-Task anlegen. Wenn der passende Agent ‚inhouse‘ läuft, kann man dann die Buildartefakte auf einen netzlokalen SMB-Share kopieren.

Fazit

Wir haben hier gesehen, wie man dynamisch beim Build eine jeweils neue Version errechnet und damit eine DevOps-Pipeline-Variable setzt und diese dann erfolgreich in die Buildartefakte (Assemblies und MSIs) hineinbekommt. Dies soll eine Inspiration sein, um jederzeit korrekt versionierte Builds zu haben und MSI-Installer, die automatische Upgrades machen können.

Multi Document Interface mit Angular

Problemstellung

Es geht in diesem Beitrag um eine Möglichkeit, in einer ASP.Net+Angular-Anwendung mehrere Dokumente und die Standardnavigation darzustellen. Man stelle sich vor, es wird eine Business-Anwendung in Angular entwickelt und diese kann z.B. Akten anzeigen. Zusätzlich hat sie aber, wie jede vernünftige Anwendung, eine Navigation über diverse Bereiche. Was man also will, ist: Es gehen für die Akten eigene Tabs auf und in einem „Haupt-Tab“ wird weiterhin die übliche Navigation durchgeführt (z.b. Profileinstellungen, andere Aspekte der Anwendung). Hier zeige ich das Beispielhaft an einer von mir entwickelten ASP.Net + Angular- Anwendung (auf Basis von ABP). Es sollte sich aber leicht auf alle Angular-Anwendungen übertragen lassen.

Router

Kurze Rekapitulation, wie man in Angular Navigiert. Die Anwendung wird als eine Hierarchie von Komponenten (components) definiert. Typischerweise mit *.html*-Dateien. Irgendwo befindet sich darin ein Element *<router-outlet></router-outlet>*. Hierdurch weiß der *Router* von Angular, dass an dieser Stelle die Komponenten des Routing-Ziels eingeblendet werden sollen. Was dann auch passiert.

Tatsächlich kann man mehrere *router-outlet*s definieren. Die Weiteren müssen individuelle Namen haben. Dann kann man mit einer tollen URL-Syntax mit Doppelpunkten und Outlet-Namen für alle Outlets gleichzeitig verschiedene Komponenten „ernavigieren“. Allerdings hat sich das meinen Kenntnissen und meiner Vorstellungskraft entzogen… Also ob man nur einzelne Outlets ändern kann, derweil der Rest bleibt und wie man dann dynamisch mehr und mehr solche Outlets erstellt (die Tabs) und auch wie man sie sinnvollerweise dann benennt. Also dieser Ansatz wurde ad Acta gelegt.

Der Ansatz

Datenhaltung

Wie sollte das Ganze sinnvoll gestaltet werden? Die Idee wäre, dass man ein Objekt hat, welches die Tabs als Datenstruktur enthält und einige Methoden bietet, um die Tabs zu verwalten. Ich habe es als Singleton realisiert. Es kann aber, je nach Architektur, auch sinnvoll sein, mehrere davon zu haben. Z.B. wenn man mehrere Tabs aufbauen will. Dann muss aber irgendwie das „default“-Router-Otulet abschaltbar werden.

Wir brauchen auf alle Fälle einen injezierbaren Service (@Injectable), der in den verschiedensten Modulen der Anwendung verfügbar ist und die Tabs programmatisch modifizieren kann. Zusätzlich muss er aber auch für die Darstellungskomponente verfügbar sein.

Code von src\app\shared\layout\open-document-service.ts:

import { Injectable,
    Inject,
    Type,
    ReflectiveInjector,
    ViewContainerRef, 
    Injector } from '@angular/core';
import { AppComponentBase } from '@shared/common/app-component-base';

// Injezierbarer Service, um auf die globale Definition
// der Tabs zuzugreifen und zu manipulieren
@Injectable()
export class OpenDocumentService {
  
    constructor(private _inj : Injector)
    {
    }

    offeneDokumente: Array<Dokument> = [  ];

    addTab(d:Dokument){
        let newId = d.id;
        const i = this.offeneDokumente.findIndex( elem=>elem.id == newId);
        if(i > -1)
        {
            // Tab aktivieren
            this.offeneDokumente[i].active = true;
            return;
        }

        // sonst kann es hinzukommen
        this.offeneDokumente.push(d);
    }

    // remove a tab
    remove(id: string) {
        const i = this.offeneDokumente.findIndex( elem=>elem.id == id);
        this.offeneDokumente.splice(i, 1);
    }
}

export class Dokument {
    public titel : string;
    public id: string;
    public removable : boolean;
    public disabled : boolean;
    public active : boolean;
    public compref: any;
}

Hier werden die Tabs in einem TS-Array gehalten und es gibt simple und eventuell verbesserbare Methoden zum Hinzufügen und entfernen. Als Struktur wurde die neue Datenklasse Dokument eingeführt, die eien Tab repräsentiert.

Darstellung

Das Ganze muss natürlich irgendwie Dargestellt werden. Zunächst steht das Control, das zur Realisierung genommen wird. Dabei habe ich mich für TabsetComponent von „ngx-bootstrap“ entschieden. Eingebunden wird das über eine neu zu erstellende Komponente in die Anwendung. Nennen wir das Teilchen mal *multdoc-control*. Im Prinzip ist diese Komponente nichts anderes als eine geschickte Tab-Definition mit ngFor. Der immer vorhandene Tab ist das „default“ *router-otulet*; und der Rest ist die per ngFor definierte Magie, mit der die offenen Dokumente/Akten dargestellt werden.

Code von src\app\shared\layout\multi-document.component.ts:

import { Component, ViewContainerRef, ViewChild } from "@angular/core";
import { OpenDocumentService, Dokument } from "./open-document-service";
import { ContentPresenterComponent } from "./content-presenter.component";
import { TabsetComponent } from "ngx-bootstrap";

// Das ist so ziemlich nur eine GUI (html zusammenfassung) Komp.
// https://valor-software.com/ngx-bootstrap/#/tabs
@Component({
    template: `  
    <div>
        <tabset #mainTabs>
            <tab heading="Titel">
                <router-outlet (activate)='onOutletActivate($event)'></router-outlet>
            </tab>

            <tab *ngFor="let tabz of offeneTabs.offeneDokumente"
                [heading]="tabz.titel"
                [active]="tabz.active"
                (selectTab)="tabz.active = true"
                (deselect)="tabz.active = false"
                [disabled]="tabz.disabled"
                [removable]="tabz.removable"
                (removed)="removeTabHandler(tabz)"
                [customClass]="tabz.customClass">
                <content-presenter [component]="tabz.compref"></content-presenter>
            </tab>
        </tabset>
    </div>`,
    selector: 'multi-document-component',
    providers: [
        OpenDocumentService,
    ],
    viewProviders: [ ContentPresenterComponent ],
})
export class MultiDocumentComponent  {

    @ViewChild('mainTabs') mainTabs: TabsetComponent;
    offeneTabs: OpenDocumentService

    constructor(private _vcr: ViewContainerRef,
         offeneTabs : OpenDocumentService)
    {
        this.offeneTabs = offeneTabs;
    }
     
    removeTabHandler(tab:Dokument){
        //alert("REM: tabid " + tab.id);
        this.offeneTabs.remove(tab.id);
    }
    
    onOutletActivate(ev): void {
        this.mainTabs.tabs[0].active = true;
    }
}

Hier ist also relativ geradelinig das router-outlet und die dynamische Definition der weiteren Tabs. Da so wenig HTML dabei ist, ist alles in eine .ts-Datei gewandert. Dazu noch einige Events, wie z.B. dass bei Start der erste Tab mit dem router-outlet aktiviert wird. Nur, wie werden diese Tabinhalte Dargestellt und on-demand erstellt? Also erst bei Tab-Klick die korrekte Komponente gefunden und eingefügt? Die Lösung ist mein selbst entwickelter Contentpresenter. Hier sieht man recht klar meine Herkunft aus der WPF-Welt. Die hat mir dabei durchaus genutzt, vielleicht hätte es aber auch eine direktere Angular-Lösung gegeben.

Code von src\app\shared\layout\multi-document.component.ts:

import { Component, OnInit, 
    ViewContainerRef, AfterViewInit, ViewChild, 
    Input, 
    ComponentFactoryResolver, Inject, ViewRef, OnDestroy } from "@angular/core";
import { OpenDocumentService } from "./open-document-service";

// Komp. wie ein ContentPresenter
// https://medium.com/front-end-weekly/dynamically-add-components-to-the-dom-with-angular-71b0cb535286
@Component({
    template: `<ng-template #dynamic></ng-template>`,
    selector: 'content-presenter',
    providers: [
        OpenDocumentService,
    ]
})
export class ContentPresenterComponent implements AfterViewInit, OnDestroy   {
    @ViewChild('dynamic', { 
        read: ViewContainerRef 
      }) viewContainerRef: ViewContainerRef

    // This property is bound using its original name.
    @Input() component: any;
    factoryResolver: ComponentFactoryResolver;

    constructor(
          @Inject(ComponentFactoryResolver) factoryResolver: ComponentFactoryResolver)
    {
        this.factoryResolver = factoryResolver;
    }

    ngAfterViewInit() {
        let view: ViewRef = this.component.hostView
        this.viewContainerRef.clear();
        this.viewContainerRef.insert(view);
        view.detectChanges();
    }

    ngOnDestroy(): void {
    }
}

In ngAfterViewInit() passiert die Magie. Generell wird eine Komponente im gewählten Tab-Control erst initialisiert, wenn sie angezeigt wird. Das ist aber eine Eigenschaft des Tab-Controls. Dann aber wird die eigentliche Zielkomponente, wie sie in compref übergeben wird, einfach nur an dieser Stelle eingefügt. Sicherheitshalber wird vorher der alte Inhalt gelöscht. Zuvor muss aber der Tab-Inhalt von irgendwem erzeugt worden sein. Dazu wird die passende „Factory“ über den ComponentFactoryResolver (angular Komponente) gesucht und die Komponente erstellt. Siehe dazu den Teil Verwendung. Wichtigster Teil hier ist die Angular-Komponente <ng-temlpate>. Sie kann im TS-Code gefunden werden und mit beliebigen anderen Views gefüllt werden.

Test

Jetzt haben wir alle Teile zusammen und können alles zusammenfügen und ausprobieren. Zum Test bietet es sich an, sich eine kleine Testkomponente zu erstellen, die möglichst wenige Abhängigkeiten hat. Hier ist ein Beispiel dafür:

import { Component, OnInit, Input, OnDestroy } from "@angular/core";

// eine Testkomponente
@Component({
    template: `  
    <div>
        Ich bin ein Test von ID={{id}}
    </div>`,
    selector: 'test-component',
    viewProviders: [  ],
})
export class TestComponent implements OnInit, OnDestroy  {
    @Input() id: string;
    
    ngOnDestroy(): void {
        alert("OnDestoy TestComp:" + this.id);
    }
    
    ngOnInit(): void {
    }
}

Durch das Implementieren von OnDestroy wird die Methode ngOnDestroy() bei Entfernung aufgerufen. Das soll durch alert() veranschaulicht werden.

Verwendung

Einbindung

Die Einbindung der neuen Multi-Dokument-Komponente sieht wie folgt aus. In meinem Fall ist es in einem ABP-Theme geschehen. Grundsätzlich gilt es, alle Vorkommen von <router-outlet> durch <multi-document-component> zu ersetzen:

        ...
        <div class="m-grid__item m-grid__item--fluid m-wrapper">
            <multi-document-component></multi-document-component>
        </div>
       ...

An dieser Stelle sei erwähnt, dass bei Angular wirklich die XML-Elemente in der Form von öffnendem und schließendem Teil geschrieben werden müssen. Sonst geht es nicht.

Steuerung

Gesteuert wird die Tab-Komponente über die injezierbare Datenhalter-Instanz. Dazu muss diese zunächst als Injectable eingebunden werden und dann kann man sie zum Beispiel so verwenden:

import { Component, Injector, ViewEncapsulation, ViewChild, Input, Type, ComponentFactoryResolver, ComponentRef } from '@angular/core';
import { OpenDocumentService, Dokument } from '@app/shared/layout/open-document-service';


...

    //--Constructor-------------------------------------------------
    constructor(
        injector: Injector,
        private _fr:ComponentFactoryResolver
        ...,
        private _openDocSrv: OpenDocumentService,
    ) {
        super(injector);
    }

addAkteToView(ka: KarteiDto): void {

        const factory = this._fr.resolveComponentFactory(TestComponent);
        
        const componentref: ComponentRef<TestComponent> = factory.create(this._injector);
        componentref.instance.id = ka.id;

        let dokument:Dokument = {
            active: true,
            titel: ka.akte,
            disabled:false,
            removable:true,
            id: ka.id,
            compref: componentref,
        }
        this._openDocSrv.addTab(dokument);
    }

In dieser ‚anderen‘ Komponente wird die einzufügende Komponente (TestComponent) aufgelöst, erzeugt und eingefügt. Also nicht im Contentpresenterdaselbst, sondern hier. Dazu braucht man den Injector und die den ComponentFactoryResolver. Nach dem Resolve kann die neue Komponente angesprochen werden und vermittels einem passenden Dokument-Objekt in den OpenDocumentService eingefügt werden. Das Ergebnis ist sofort sichtbar. Die Elementvariable compref übergibt die eigentliche Komponente.

Gedanken

Ich hatte im ersten Moment natürlich schon geplant, die Instantiierung der View im Tab in diesen ContentPresenter hineinzulegen. Tatsächlich ist der Stand ja, dass die „tabanlegende“ Komponente die View instantiiert. Vorteil dieser jetzigen Vorgehensweise: Man kann die neue Komponenteninstanz, die in den Tab hineinkommt, noch weiter konfigurieren. Also an der Stelle im Code, wo das meiste Fachwissen zu dieser Komponente vorhanden ist. Würde man die Komponente im ContentPresenter instantiieren, könnte man sie nicht individuell konfigurieren – man bräuchte beispielsweise einen weiteren Callback. Diesen Weg wollte ich durchaus beschreiten, bin aber letztendlich ein wenig vor der höheren Komplexität zurückgeschreckt (war bei dieser Lösung auch nicht nötig) und ich habe es syntaktisch nicht hinbekommen, den generischen Typ ComponentRef<Comp> und den Typ Comp parametrisch in eine Schnittstelle zu gießen, sodass dieser Zweisatz funktionieren könnte:

const factory = this._fr.resolveComponentFactory(Comp);
const componentref: ComponentRef<Comp> = factory.create(this._injector);

Indien – eine kurze Einführung

Hier nun mein kurz und knapp reisehinweise. Zu Indien. Wie immer wichtig sind Internet Mobil und Geld.

Anreise

Praktischerweise reist man mit dem Flugzeug an und weil Delhi eh die schlechteste Luft aller Städte hat, sind auch dorthin die Flüge am günstigsten. Tja, vielleicht nicht deshalb, aber im Jahr 2019 bot es sich an, einen Hin- und Rückflug via Delhi zu machen, irgendwo hin zu reisen und von irgendwo einen Einwegsflug nach Delhi zu nehmen, um zurückzukehren.

Geld

Bezüglich Geld ist Indien ein ganz normales Land, welches im SWIFT System dabei ist. Man geht mit seiner Karte an einen Automaten (ATM) und bekommt Geld. Wie in anderen Ländern auch, gibt es auch in Indien Geldautomaten, die mit internationalen oder auch nur fremden Karten anderer Institute nichts anfangen können. Dann gibt es etliche die 200 Rupien Gebühr wollen. Um die solle man einen Bogen machen, denn es gibt genügend Automaten, die keine Gebühr verlangen. Davon im Weiteren eine unvollständige Liste. Aktuell, zum Zeitpunkt des Schreibens, konnte man maximal 10.000 Rupien pro Vorgang abheben. Das waren damals ca. 128€. Ein durchaus sinnvoller Wert. Als Karte für Auslandsreisen empfehle ich nach wie vor die VISA-Kreditkarte der comdirekt.de. die erlaubt es quasi gratis Geld abzuheben. Keine auslandsgebühr und keine Automatengebühr. Abrechnung fast zum Tagesmittelkurs.Bekannt gute Banken:

  • SBI (State Bank of India)
  • IndusInd Bank
  • Baroda Bank
  • HDFC Bank
  • Dhanlaxmi Bank

Mobilfunk

Es geht doch nichts über die Nützlichkeit von mobilem Internet an der Hand und zu jeder Zeit. Insofern plädiere ich schon lange für simkarten wo immer man ist.Indien ist diesbezüglich, wie in vielen Dingen, bürokratisch und etwas komplizierter. Natürlich muss man seinen Pass vorzeigen und sich registrieren. Mit auf der Straße einfach so kaufen iss nich. Könnte ja sonst auch jeder Terrorist was schlimmes damit machen…Daher kann aber nicht jeder Laden so eine SIM-Karte ausgeben. Es empfiehlt sich daher, gleich den oder die Läden am Flughafen zu nutzen. Ich kann nur zu Delhi berichten. Angeblich gibt es im Ankunftsbereich einen Air-Tel und einen Vodafone-Laden. Beide habe ich zusammen mit einem Freund nicht entdeckt. Stattdessen sind wir am ersten Tag zum Conought-Place und haben den dortigen (haupt-)Vodafone-Laden aufgesucht, da dieser als einer der wenigen auch mit fremden Pässen umgehen kann. Das war dann auch eine Prozedur. Eben typisch indische Überbürokratie. Man muss nämlich neben dem Pass auch das E-Visa und ein Foto mitbringen. Dankenswerterweise nutzt man Vorort Kopierer und kopiert die Pässe und E-Visa und kann sogar Bilder machen. Kosten? Kopie nix; Bilder hatte ich dabei. Nach endlosen Formularen und Papier und getackere bekommt man irgendwann eine SIM-Karte. Die muss dann noch aktiviert, aufgeladen und der passende Tarif gewählt werden. Zum Glück macht das alles der Mensch im Laden für einen. Sonst würde das ja noch länger dauern. Aber deshalb gibt es ja auch handgeschriebene Wartemarken – ganz wie auf dem Amt. Der Tarif ist dagegen recht gut: Für umgerechnet 9€ hatte ich 28 Tg. 1,5GB Internet und 4G sowie 500 Minuten Gespräch.

Strom

Der Strom ist in Indien kompatibel. Mit seinen 240V 50 Hz hat er zwar 10V mehr, aber das macht nichts. Von den Steckern her ist es verwirrend aber praktisch problemlos. Eigentlich hat Indien den alten britischen Standard bei sich im Einsatz. Das sind zwei Stecker, wo die Pinne im Dreieck angeordnet sind. Einmal dicker und einmal etwas runterskaliert. Indien hat im Gegensatz zu vielen anderen Ländern nie den modernen britischen Standard (den mit den rechteckigen Pinnen) adaptiert. Allerdings ist fast überall eine von zwei verschiedenen Universalsteckdosen zu finden. Die nehmen entweder den kleinen indischen Stecker auf oder US oder EU-Stecker sowie manche auch britische. Oder man bekommt den EU-Stecker auch in die kleine indische Steckdose hinein.

Aber: Wenn man sich mal ansieht, wie der Strom so verlegt ist, könnte einem als Elektriker schlecht werden. Als „Normalo“ sollte einem dabei so mulmig werden, dass man ständig in einer gewissen Hab-Acht-Stellung ist. Denn es hängen Drähte und Kabel überall herunter oder so tief, dass man sie mit dem Kopf berühren kann. Das Gute ist: Es handelt sich fast immer um tote Datenkabel, die so oder so harmlos sind. Doch: Man weiß eben nie. Daher lautet das Gebot: Berühre nichts, was von oben herabhängt. Achtung ist auch geboten bei allen elektrischen Verteilungen. Ob Trafostation oder Hausanschluss. Überall findet man nicht-berührsicher ausgeführte Ekeltroinstallationen. Also berührbare spannungsführende Teile, die im Zweifelsfall tödlich sein können. Daher: Immer einen gesunden Abstand wahren.

Wasser

Wasser oder die Wasserqualität ist das große Problem von Indien. Ein hygienisch gefährliches Land unterscheidet sich von einem hygienisch ungefährlichen anhand der Qualität des Leitungsnetzes und der Wasserqualität. Beides ist in Indien desolat. Das Wasser in Indien muss als bakteriell kontaminiert angenommen werden. Also wascht euch damit bestenfalls ab, aber nutzt es nicht zum Zähne putzen und schon gar nicht zum Trinken. Also Zähne putzen mit der PET-Wasserflasche. Doch auch da gilt es aufzupassen, denn es soll Fälle gegeben haben, wo vom Händler eigenhändig wiederbefüllt wurde. Tee oder andere gekochte Getränke sind OK. Also auch im Hotel den Wasserkocher benutzen etc. Tip: Tassen einmal mit kochend heißem Wasser ausspülen. Auch Teller, die noch Wasserreste drauf haben sind potentielle Keimüberträger. Das gute: Trockenwischen und 3min der Luft aussetzen reicht i.d.R. aus um sicher zu sein.

In Restaurants wird i.d.R. Wasser aus Kannen angeboten. Für Inder ist das wunderbar geeignet, für uns ‚Westler‘ eher nicht. Vermutlich passiert nichts, aber besser man bleibt auf der sicheren Seite.

Hotelerie

Indien ist so ein naja, komisches Hotelland. Zunächst gibt es sehr viele Hotels zu auch sehr günstigen Preisen und auch viele Kategorien. Man könnte jetzt positiv sagen, für jeden sei Etwas dabei. Man findet also auf den üblichen Hotelbuchungsportalen viel Auswahl, aber nicht alles. Insbesondere ist booking.com und Expedia (hotels.com) nicht vollständig. Macht aber nichts. Die meisten Hotels gehören Leuten, die da nie selbst „Im Laden“ stehen oder Ketten. Betreut werden sie auch eher von „durchschnittlichen“ Menschen. Ego ist es nicht sonderlich erfolgreich, eine Buchung per Telefon zu machen. Insbesondere kommt man nicht billiger ‚raus, als wenn man via Buchungsportal geht. Das verwundert zunächst, heißt aber, dass die Angestellten dort nichts zu sagen haben und die Preise woanders gemacht werden. Daher: Um Missverständnisse zu beseitigen und klare Preisangaben zu haben, sollte man in Indien über Buchungsportale wie booking.com und Hotels.com gehen. Tatsächlich wird es so günstiger als direkt.

Die Qualität ist eine andere Sache. Man sollte auf jeden Fall auf Buchungsportalen vergleichen. Es gibt immer wieder gute Preisbrecher. Aber gegen schimmlig und schlecht gewartet ist leider kein Kraut gewachsen. Bilder kann man nicht riechen und Bilder werden auch sehr gerne stark geschönt. Da kommt dann die große reale Ernüchterung. Es empfiehlt sich daher jeweils nur eine Nacht zu buchen, um die Kosten und Zimmerart festzulegen und dann nach Bedarf verlängern. Schimmel und übel ungewartete Hotels sind leider häufiger als einem lieb ist und bei den Angestellten dann leider auch überhaupt kein Bewusstsein dafür da.

3 Monate im Jahr Urlaub

Wie geht das denn? 12 von 52 Wochen. Das klingt ja schon fast wie die berühmte 4-Stunden-Woche.

Als Angestellter wird 3 Monate Urlaub eher nichts, außer man macht einfach unbezahlten Urlaub. Als Freiberufler oder Gesellschafter-Geschäftsführer klappt das schon eher, wobei man als Freiberufler in seinem Urlaub eigentlich gar nichts verdient. Aber wie es eben so ist als Selbständiger: Man verdient ja etwas mehr als ein Angestellter und muss sich seinen Urlaub selbst hin-finanzieren. Wenn man es richtig anstellt, reichen 8 Monate Arbeit, um sich die restlichen 3 Monate ein sein kalkulatorisches oder im Geschäftsführer-Fall sein tatsächliches Gehalt zu zahlen.

Urlaubskosten

Wohl die Rechnung ohne den Wirt gemacht? Urlaub ist doch immer teuer und dann noch in einer Zeit, in der man irgendwie ja nichts verdient. Das ist schon richtig. Urlaub kann sehr teuer sein, aber er kann auch recht günstig sein. Ich habe es aufgrund meiner Länderwahl zuletzt immer geschafft, weniger Geld auszugeben, als wenn ich daheim geblieben wäre. Das ist natürlich auch nicht ganz richtig, weil ja die Miete für die Wohnung weiter läuft. Aber wenn wir die mal als Grundkosten Beiseite lassen, dann lässt sich durchaus pro Tag mit weniger Geld zurechtkommen, als in Deutschland. Daher empfehle ich Balkan, Osteuropa und Süd-Ost-Asien als gute Reiseländer.

Machen

Vor allem aber ist es mal wieder eine Mentalitätssache. Man traut sich nicht, länger unbezahlten Urlaub zu machen, oder ohne Folgeprojekt in einen 2-Monatigen Urlaub zu gehen etc. pp. Aber an dieser Stelle muss man einfach seine Bedenken über Bord werfen und es machen. Mut zur Lücke. Viel passieren kann nicht. Wenn man so jedes Monat gut über die Runden kommt, dann auch in einem Reisemonat. Auf geht’s.

Monopoly – Das Phänomen

Monopoly, das Spiel. Das, das jeder kennt und vermutlich schon einmal gespielt hat. Das Phänomen. Warum, Phänomen? Weil es soo ein schlechtes Spiel ist, und dennoch wohl das meistverkaufte jemals ist. Schlimmer nich: Das Spiel existiert in unzähligen Varianten. Es gibt Varianten für diverse kleine und große Städte, Disney, Länder, Filme. Vor allen aber ist Monopoly irgendwie immer eines der Spiele, die im Verkaufsregal zu finden ist, egal wie klein die Spieleabteilung ist. Traurig. Am Ende aber ist das Spielprinzip immer gleich… gleich schlecht, wie ich finde.

Schlechtes Spielprinzip

Gute Spiele zeichnen sich dadurch aus, dass ein gewisses Machtgleichgewicht bis mindestens zur Hälfte der Spielzeit besteht. Das kann dadurch erreicht werden, dass es mehrere gleichwertige Strategien gibt, mit denen man das Spiel gewinnen kann, statt nur einer. Bei Monopoly ist genau das nicht der Fall. Das Gleichgewicht kippt sehr früh im Spiel und von da an Belgier m verstärkt sich das Ungleichgewicht zusehends. Recht bald macht das Spiel nur mehr einem Spieler Spaß, der Rest muss im wahrsten Sinne gute Mine zum bösen Spiel machen. Aus diesem Grunde ist Monopoly für mich eines der schlechtesten Spiele und das schlechtest- und meistverkaufte Spiel der Welt. Echt traurig.

Appell

Daher bin meiner Seite der Appell am alle Spieler und Schenkenden. Spielt nicht dieses Spiel, egal welcher Variante; es bedeutet nur Frust und hat in der Regel nichts mit Können oder Unvermögen zu tun. Und bitte kauft es nicht und verschenkt es nicht… vor allem nicht in der 100-sten Variante. Lasst die Kisten in den Verkaufsregalen verrotten!

Grenzübertritt Turkmenistan-Usbekistan

(bei Farab)

Ein Erfahrungsbericht über das Passieren der Landgrenze von Turkmenistan nach Usbekistan. So ist es uns ergangen. Unsere Zeit: ca. 1:15 h. Unterwegs zu Fuß zu zweit mit je einem Koffer/Rucksack. Bei Euch kann es natürlich völlig anders laufen und vielleicht Stunden dauern.

Aufbau

Der Grenzübergang zwischen den beiden Ländern ist für diese Region vielleicht typisch, aber im Vergleich zu Europäischen Grenzübergängen schon ziemlich aufwendig. Man kann ihn grob in 7 Stationen einteilen. Hier eine Übersicht:

Satelitenansicht des Wegs über die Grenze
Grenzanlage Farab

Prolog aka. Station 0 (TM)

Eigentlich beginnt die Grenze schon auf der Brücke 2km davor. Wie im ganzen Land gibt es auch hier einen Polizeicheckpoint. Hier am Checkpoint lässt man nur Leute durch, die zur Grenze wollen. Pässe bereitlegen.

Station 1 – Checkpoint (TM)

Hier vor der Schranke wurden wir von unserer Tour (ja die braucht man in Turkmenistan) abgesetzt. Vor der Schranke stand eine Traube von ca. 25 Leuten in ca. 40°C und praller Sonne und wollten durch. Der Checkpoint aber verweigerte es bis auf Weiteres. Wir machten uns schon mal auf Etwas gefasst. Aber kaum das klar wurde, dass wir Touristen waren und EU-Pässe in Händen hielten, wurden wir zum Schalter gebeten. Ein Militärmensch schrieb manuell unsere Passnummern und Namen in ein Buch ein. Sodann durften wir zu den anderen, auf das „Taxi“ wartenden ca. 20 Leuten rübergehen. Das „Taxi“ ist ein alter Barkas und musste erst einmal kommen. Um Zeit und Geld zu sparen, wollten wir die 800m zu Fuß gehen, was uns aber verweigert wurde. Entweder aus Ungläubigkeit oder aus Angst, wir könnten vom Weg abkommen. Also warteten wir auf die Wiederkehr des Barkas‘. In der zweiten Fuhre konnten wir mitfahren. Es kostete je 1 Manat, was ca. 6ct entsprach. Wahlweise wurde auch 1 USD genommen (höhö).

Station 2 – Grenzkontrolle (TM)

Die eigentliche Ausreise. Wir kamen in dem 800m entfernten Gebäude an und mussten kurz anstehen, da vor dem Ausreiseschalter zu viele Menschen standen (Stau). Dann ging es los mit Gepäckscan. Nicht sonderlich gründlich – einfach durch da. Unklar, ob überhaupt jemand auf den Bildschirm geschaut hat. vor uns war der Schalter mit dem Beamten, der die Ausreisestempel einstempelt. Davor eine Menge von ca. 34 Turkmenen, die chaotisch mit Ihren Pässen in den Händen um „Abstempelung“ flehten. Der Beamte schob ruhig seinen Dienst. (Anstellen in einer Reihe war hier wohl nicht vorgesehen). Ein Beamter vom Scanbereich eilte herbei und machte uns einen kleinen Zugang zu dem Schalter frei, wo wir auch prompt unsere Pässe hineingeben sollten. Nachdem der Stempel-Mensch mit den aktuellen Turkmenen-Pässen fertig war, kamen auch schon unsere dran – das ging merklich schneller als mit dem turkmenischen Pass. Mit abgestempeltem Visum konnten wir das Gebäude wieder verlassen und mussten davor auf das nächste „Taxi“ (ebenfalls ein alter Barkas) warten. Zum Glück gab es hier Schatten. Das „Taxi“ kam auch recht bald. Es stand wieder eine Traube von Menschen bereit zum Einsteigen. Wir waren uns schon sicher, hier auch erst mit der zweiten Fuhre mitzukommen. Doch der Fahrer bekam irgendwie Wind von unserem Touristen/EU-Status und bat uns an der wartenden Menge vorbei, als erstes einzusteigen. Uns war es ein wenig peinlich aber auch hier wollten wir nicht lange zögern. Das Einsteigen gestaltete sich dann etwas „stapelig“ und der Fahrer bekam auch einmal so derart die Unruhe, dass er den Motor abstellte. Scheinbar hat sich jemand schlecht behandelt gefühlt und wollte ob überladener Verhältnisse unbedingt noch mitsamt Koffer mit. Irgendwie ging es.

Station 3 – Demarkationslinie (TM)

Die Fahrt ging vorbei an unzähligen wartenden LKW. Nach dem Aussteigen standen wir ein letztes Mal vor einem Durchgang mit turkmenischem Beamten. Dieser checkte noch einmal, ob auch wirklich ein Stempel im Pass war. Wartezeiten gab es hier kaum, da es jeweils schnell ging und nur diese Autoladung da war.

Station 4 – Demakrationslinie (UZ)

Direkt 2m dahinter stand der erste usbekische Beamte. Der musste natürlich auch den Pass anschauen. Dabei ging es wohl nur um die generelle Frage ob es ein gültiger für Usbekistan ist.

Station 5 – Gesundheitsamt (UZ)

Auf usbekischer Seite kommt man nach einigen Decametern (10m) zunächst in ein längliches Haus, wo am ersten Schalter ein Arzt stand. Der schaute auch kurz auf den Pass und waltete seines Amtes insofern, als dass er die Körpertemperatur maß. Er tat dies mit einem berührungslosen IR-Scanner. Wenn das durch ist, geht man noch an 2 leeren Schaltern vorbei und nach einer kurzen Hofüberquerung landet man in…

Station 6 – Einreise und Zoll (UZ)

Das ist wieder ein ordentlicheres Gebäude. Man läuft auf einen Doppelschalter zu, bei dem 1-2 Grenzbeamte die Einreise mit Einreisestempel erledigen. Das geht recht kurz und Schmerzlos. Man wird (in unserem Fall) in schlechtem Englisch durch eine kaum schalldurchlässige Glaswand noch auf ein wichtiges Detail hingewiesen: Die Registrierungszettel der Hotels sind tunlichst aufzuheben, sonst gibt es Probleme.
Der hintere Teil der Halle gilt wieder der Sicherheit respektive wider der Einfuhr illegaler Produkte. Der Rucksack muss abermals auf einen Gepäckscanner. Danach entscheiden noch zwei Beamte mit Tischen vor sich, ob sie in dreckiger Unterwäsche wühlen wollen, oder nicht. Potentiell lassen sie EU-Bürger eher in Ruhe. Ein kleines Gespräch darüber, was man vor hat, und ob man Drohnen einführen will, wird man wohl nicht vermeiden können. Wir wurden bei der Gelegenheit auch nach dem Wohlbefinden von Hitler gefragt. Ich konterte: „Dem geht‘s jetzt ganz gut, der ist letzte Woche gestorben“. Nach einigen weiteren Wortwechseln durften wir den Bau verlassen.

Station 7 – der letzte Checkpoint

Kurz bevor man Usbekistan als freier Mensch betreten darf, kommt noch ein Posten. Der schaut dann nochmal im Pass nach, ob auch wirklich ein aktueller Stempel für Usbekistan drin ist.

Der Taxistand

Direkt hinter der Grenze endet die Straße von Buchara. Hier befindet sich ein Taxiplatz und die Endstation diverser Kleinbusse (Marschrutka). Taxifahrer wollen für die ca. 100km nach Buchara 50 USD oder auch 50 EUR. Mit dem Faustpfand, auf jeden Fall per Sammeltaxi ersteinmal nach Olot zu fahren, konnten wir einen Taxifahrer dazu bringen, sich selbst auf 20 USD herunterzuhandeln. Dazu haben wir ihm zunächst 10 $, dann 20 $ vorgeschlagen und uns demonstrativ in das Sammeltaxi hineingesetzt.

Meta

Auf usbekischer Seite ist viel weniger los, als auf der turkmenischen Seite. Das Problem liegt eindeutig an der Ausreisestelle in Turkmenistan. Sobald ein Turkmene erfolgreich ausgereist ist, machen ihm die Usbeken wohl keine Probleme mehr. Daher war auf usbekischer Seite wenig los.