Die vielen Varianten von HttpClient

HttpClientHandler

Wer Xamarin verwendet, hat die Möglichkeit, den Standard-.NET-HttpClient zu verwenden. Standardmäßig ist HttpClient eine komplette Reimplementierung des gesamten HTTP-Stacks in Mono. Für viele Anwendungsfälle reicht dies aus, allerdings kann durch die Wahl eines alternativen HttpClientHandler ein unterschiedliches Verhalten erreicht werden. Für meinen Vortrag auf der Evolve habe ich eine Übersicht der unterschiedlichen HttpClientHandler zusammengestellt:

HttpClientHandlers

CFNetworkHandler (iOS 6+) und der neue NSUrlSessionHandler (iOS 7+, ab Xamarin.iOS 9.8) sind Handler, die Apple native APIs statt der Mono-Implementierung verwenden. Der Standardhandler, der beim Aufruf des Defaultkonstruktors von HttpClient verwendet wird, kann entweder in der IDE oder durch Übergabe einer Option an mtouch (z.B. --http-message-handler=NSUrlSessionHandler) beeinflusst werden.

iOS-Optionen: HttpClientHandler

Für Android gibt es jetzt AndroidClientHandler (ab Xamarin.Android 6.1). In der IDE gibt es keine Option zur Definition des Standardhandlers, allerdings lässt sich der Handler mit Hilfe einer Textdatei mit der @(AndroidEnvironment) Build Action die Umgebungsvariable XA_HTTP_CLIENT_HANDLER_TYPE auf den Wert Xamarin.Android.Net.AndroidClientHandler setzen.

Alternativ kann aus ModernHttpClient der NativeMessageHandler an den HttpClient-Konstructor übergeben, wordurch auch native Implementierungen für HTTP-Aufrufe verwendet werden.

SSL/TLS

Die Standardimplementierung in Mono unterstützt leider im Gegensatz zu den nativen Handlern nicht den neuesten (und sichersten) TLS-Standard 1.2. Um TLS 1.2 mit der Mono-Implementierung zu verwenden, hat Xamarin.iOS 9.8 eine Möglichkeit eingeführt, die TLS-Implementierung durch P/Invoke-Aufrufe in die TLS-Implementierung von Apple zu ersetzen. Dies kann entweder über die IDE oder die mtouch-Option --tls-provider=appletls aktiviert werden.

iOS-Optionen: TLS

Für Android gibt es aktuell keine solche Option, allerdings wird erwartet, dass bald BoringSSL-Support hinzugefügt wird.

Hier ist die Zusammenfassungsfolie aus meine Vortrag:

HttpClient comparison

Xamarin hat tatsächlich eine Reimplementierung des TLS-Codes erstellt, um auch TLS 1.1 and 1.2 zu unterstützen. Es wird allerings erwartet, dass diese Implementierung aus Sicherheitsbedenken verworfen und stattdessen die nativen Implementierungen der jeweiligen Plattformen herangezogen werden, so wie Microsoft das seit jeher schon mit Windows macht.

Update (2017-02-15)

Hier ein Update zum aktuellen Stand zu HttpClient:

  • Man kann jetzt in den Projekteinstellungen von Android-Projekten angeben, dass man den AndroidClientHandler verwenden will, so wie es bereits für iOS möglich war.
  • Wie erwartet hat Xamarin TLS-1.2-Unterstützung zum Mono-HttpClientHandler hinzugefügt durch die Einbindung von Googles BoringSSL. For Android ist diese Option ebenfalls in den Projekteinstellungen auswählbar. BoringSSL bringt TLS 1.2 auch für die Unix/Linux-Implementatierungen von Mono.
  • Entgegen meinem bisherigen Kenntnisstand unterstütz ModernHttpClient doch support Certificate Pinning mit Hilfe von ServicePointManager. Thomas Bandt hat einen exzellenten Blogbeitrag darüber geschrieben, wie man Certificate Pinning mit ModernHttpClient oder sogar AndroidClientHandler zum Laufen bekommt.

Und hier ist die aktualisierte Matrix:

HttpClient comparison

Wow, ich bin ein Microsoft MVP!

Am 1. April 2016 wurde mir der Microsoft-MVP-Status für „Visual Studio and Development Technologies“ verliehen. Für mich ist das eine große Ehre und ich bin immer noch ein bisschen baff. Ich habe in der kurzen Zeit seit Beginn des Monats schon mitbekommen, auf welche neuen Informationen ich jetzt Zugriff erhalte und habe neue Kontake geknüpft. Ich freue mich sehr auf das, was noch bevorsteht.

Der überraschende Teil war, dass ich meine Community-Arbeit fast ausschließlich auf die Xamarin-Technologie beschränkt hatte. Oktober 2015 wurde durch eine Änderung der Kategorien für das MVP-Programm plötzlich Xamarin als eine der möglichen Technologien aufgeführt, für die der MVP-Award vergeben wird (hey, auf der Liste steht sogar Java!).

MVP-Award-Kategorien

Ich wurde von Microsofts Technical Evangelist Daniel Meixner Ende 2015 nominiert (vielen Dank!), dann gab es ein Review, und offensichtlich haben meine Beiträge zur Community der Jury gereicht.

2016 soll weitergehen wie 2015. Anfang des Jahres habe ich zum MvvmCross-4.0-Relase beigetragen, ich habe schon zwei öffentliche Vorträge gehalten und die Zeit gefunden, um zu bloggen. Jetzt, da die Xamarin-Evolve-Konferenz vor der Tür steht (auf der ich auch einen Vortrag halten werde) bin ich gespannt, in welche Richtung Xamarin und jetzt Microsoft das Cross-Platform-Abenteuer führen werden. Die Ankündungen auf der Build-Konferenz haben mich persönlich schon sehr glücklich gemacht, und ich freue mich darauf, das Wissen in Zukunft mit noch mehr Entwicklern teilen zu können.

Wer einen meiner Vorträge sehen will, sollte auf meiner Liste öffentlicher Vorträge nachsehen. Dort werde ich auch Links zu Folien oder Videos für all diejenigen posten, die den Vortrag verpasst haben.

Vielen Dank vor allem auch an meinen Arbeitgeber Zühlke, der mich bei der Communityarbeit mit Zeit und Geld unterstützt!

C/C++-Bibliotheken aus Xamarin-Code aufrufen

Mit Hilfe der Xamarin-Plattform lassen sich iOS- und Android-Anwendungen vollständig in C# entwickeln. Es gibt jedoch Fälle, in denen existierender Code zu groß oder komplex ist, um eine Portierung nach C# sinnvoll werden zu lassen. Die meisten Beispiele, die man online findet, zeigen, wie man existierenden Objective-C-Code in Xamarin.iOS und existierenden Java-Code in Xamarin.Android einbindet, obwohl es durchaus möglich ist, C-Code aus einer Xamarin-App aufzurufen. Seit kurzem unterstützt Microsofts C/C++-Projekte für iOS und Android in Visual Studio, wodruch diese Aufgabe deutlich einfacher wird.

Die einzubindende-Bibliothek muss im Quelltext vorliegen, um sie für iOS und Android zu kompilieren. Der Code darf keine Bibliotheken verwenden, die nur in Binärform für eine andere Plattform kompiliert vorliegen.

Als erstes sollte ein neues „C++ Cross Platform Project“ mit Visual Studio 2015 erzeugt werden.
Neues Cross-platform-Projekt erzeugen
Hierzu muss das passende Visual-Studio-Paket bei der Installation ausgewählt worden sein.
Visual Studio Setup Cross-Platform Mobile C++
Hierdurch werden drei Projekte erzeugt: Eins für Android, eins für iOS und ein Shared Project.
Projektstruktur

Hierzu muss man das Shared-Project-Konzept verstanden haben. Der Code im Shared Project wird, anders als andere Visual-Studio-Projekte, nicht in eine DLL oder Bibliothek übersetzt. Ein Shared Project kann jede Art Datei beinhalten. Projekte, die das Shared Project referenzieren, können auf den Code in jeder Datei im Shared Project zugreifen. Wird Code nicht referenziert, wird er auch nicht kompiliert. Aus diesem Grund ist es notwendig, Aufrufe in den geteilten Code in die plattformspezifischen Projekte aufzunehmen. Dies heißt, dass auch die plattformspezifischen Projekt Code enthalten müssen. Der Code kann für Android und iOS identisch sein und aus der jeweils gleichen Datei stammen. Im Gegensatz zu .NET wird hierfür kein File-Linking benötigt, da der Code nicht Projektverzeichnis oder -unterverzeichnis liegen muss.

Im Beispiel habe ich eine einfache Funktion clib_add_internal() erstellt, die zwei Integers addiert und im Shared Project liegt.


Dieser Code wird aus einer Funktion in den plattformspezifischen Projekten aufgerufen.

Das ergibt für das Beispielprojekt eigentlich keinen Sinn. In einem echten Projekt würden man den Code, der aus dem .NET-Code aufgerufen werden soll (das Interface) in die plattformspezifischen Projekte nehmen und den darunterliegenden Code in das Shared Project.

Die Aufrufe aus .NET heraus erfolgen mit Hilfe von P/Invoke, da C++/CLI für iOS und Android nicht verfügbar ist. Daher müssen auch die übrlichen Regeln für P/Invoke für den Interface-Code eingehalten werden: Entweder ist der Code reiner C-Code oder, wenn C++ verwendet wird, sind die Aufrufe entweder einfache Funktionen oder statische Methoden und sie beinhalten nur POC-Parameter und Rückgabewerte.

Für das iOS-Projekt muss das Outgabeformat in statische Bibliothek (.a-Datei) geändert werden, da iOS kein dynamisches Nachladen von Code erlaubt. Für Android sollte eine dynamische Bibliothek (.so) erstellt werden.

iOS-Einstellungen
Android-Einstellungen

Das Android-Projekt lässt sich komplett in Visual Studio kompilieren. Für iOS wird jedoch eine Verbindung zu einem Mac-Build-Agent benötigt. Dieser ist nicht der aus der normalen Xamarin.iOS-Installation sondern ein eigenes Microsoft-Tool, das unter OS X mit Hilfe von npm installiert wird. Visual Studio kommuniziert mit dem Tool über TCP/IP.

Verbindung zum Build Host

Um den nativen Code aufzurufen sollte ein Xamarin-Projekt für je iOS und Android erstellt werden. Die nativen Bibliotheken sollten in die Xamarin-Projekte eingefügt werden.

Das iOS lässt sich mit dem Plattform-Dropdown für unterschiedliche Prozessorarchitekturen kompilieren (um z.B. iPhones vor dem iPhone 5s zu unterstützen, oder auch den iOS-Simulator). Mit Hilfe des Tools lipo lassen sich die Bibliotheken für die unterschiedlichen Plattformen zu einer Bibliothek vereinen (siehe Xamarin-Dokumentation). Unter Android muss die .csproj-Datei von Hand angepasst werden, um die unterschiedlichen Plattformen anzugeben (siehe Xamarin-Dokumentation).

Die Aufrufe in den nativen sind normale P/Invoke-Aufrufe (obwohl DLLs hier nicht im Spiel sind). Unter Android muss der Name der Shared Library spezifiziert werden.

Unter iOS muss der Bibliotheksname __Internal angegeben werden, um einen Aufruf in die statisch Bibliothek zu machen.

Und das wars! Der Beispielcode liegt unter https://github.com/lothrop/XamarinNative. Im Beispiel habe ich den C#-Marshaling-Code in ein weiteres Shared Project gelegt, das jeweils vom Xamarin.iOS- und vom Xamarin.Android-Project eingebunden wird.

iOS-Oberflächen mit MvvmCross

Wer MvvmCross in einem Cross-Platform-Mobile-Projekt einsetzt hat die Auswahl zwischen vier verschiedenen Ansatzen zur UI-Erstellung. Xamarin.Forms ist einer der möglichen Ansätze, der häufig eine gute Wahl darstellt (auch wenn es zurzeit noch keinen Designer hierfür gibt). Wer allerdings sehr genaue visuelle Anforderungen hat, fährt häufig mit eine nativen UI besser. Hierfür gibt es drei verschiedene Herangehensweisen:

  • Handkodiert: Alle visuellen Elemente werden im Code erzeugt. Dieser Ansatz ist unter iOS weiter verbreitet als auf den meisten anderen beliebten Plattformen. Hierdurch erhält man die maximale Kontrolle über alle Elemente der Benutzeroberfläche.
  • XIB-Dateien: XIB-Dateien sind XML-Dateien, die die visuellen Elemente beschreiben. XIB-Dateien werden üblicherweise nicht von Hand editiert, sondern mit Xcode’s Interface Builder erstellt. Seit Xamarin 4 gibt es auch in Xamarin Studio oder Visual Studio einen visuellen XIB-Editor.
  • Storyboard-Dateien: Dieser Ansatz ist der neueste. Storyboards sind ebenfalls XML-Dateien, die visuell mit Xcode, Xamarin Studio oder Visual Studio bearbeitet werden. Im Gegensatz zu XIB-Dateien enthalten Storyboards jedoch typischerweise mehrere Views zusammen mit einem Workflow, der den die Navigation zwischen diesen Views beschreibt.

MvvmCross unterstützt alle drei nativen Ansätze.

Handkodiert

Dieser Ansatz produziert offensichtlich die größte Anzahl Codezeilen. Für Entwickler, die gerade erst mit iOS anfangen, kann dies anfänglich abschreckend wirken. Der Vorteil: Keine Kampf mit den UI-Designern.

XIB-Dateien

Wer schon auf Xamarin 4 setzt und einen visuellen UI-Design-Editor bevorzugt, ist mit dieser Lösung wahrscheinlich am besten aufgehoben. Mit Xamarin Studio oder Visual Studio wird ein neuer XIB View Controller erzeugt. Damit dieser MvvmCross verwendet, muss die Basisklasse von UIViewController auf MvxViewController geändert werden. Im Beispiel unten wurden mit Hilfe des Designers drei UI-Elemente erzeugt. Als Namen wurden im Eigenschaftenfenster die (unkreativen) Namen UiLabel, UiTextField und UiButton definiert. Diese Elemente konnten dann im Data-Binding-Code angesprochen werden, der bei allen drei Ansätzen gleich aussieht.

Elementnamen setzen

Storyboard-Dateien

Der große Vorteil von Storyboards, die Möglichkeit, den Screenflow im Designer zu definieren, passt nicht so gut zum Navigationsansatz von MvvmCross. Wenn man einen Übergang von View A zu View B im Designer beschreibt, gilt dieser Übergang nur für iOS. Meist ist es sinnvoller, diese Navigationslogik in die plattformübergreifenden ViewModels zu verlagern. Daher ist es am besten, einen ein-ViewController-pro-Storyboard-Ansatz zu wählen, wenn man Storyboards mit MvvmCross verwendet.

Die Storyboard-ID setzen

Im Editor sollte die Storyboard-ID gleich dem Namen des ViewControllers gesetzt werden. Wie beim XIB-Ansatz sollte die Basisklasse von UIViewController auf MvxViewController geändert werden. Als nächstes sollte ein Konstruktor erstellt werden, der einen IntPtr an den Konstruktor der Basisklasse weiterreicht. Im letzten Schritt wird noch die ViewController-Klasse mit dem [MvxFromStoryboard]-Attribut dekoriert.

Zusammenfassung

Die gute Nachricht: Zum Projektstart muss die Wahl des Entwicklungsansatzes nicht getroffen werden. MvvmCross erlaubt die Erstellung jeder einzelnen View mit dem besten Ansatz für die jeweilige View. Es muss nur sichergestellt werden, dass es nicht mehrere Views für das gleiche ViewModel und die gleiche Plattform gibt.

Ich habe die Beispiele im MvvmCross Starter Pack Nuget (ab 4.0.0-beta8) so aktualisiert, dass eine XIB-Datei statt dem bisherigen handkodierten Ansatz mit absoluten Koordinaten (aus der Zeit vor Auto Layout) verwendet wird. Dies sollte den Einstieg in die iOS-Welt für erleichtern.

iOS-Views scrollbar machen

Wer iOS Auto Layout verwendet (was zu empfehlen ist) und je versucht hat, im Interface Builder oder Xamarin iOS Designer eine Scroll View hinzuzufügen, wird festgestellt haben, dass dies keine einfache Aufgabe ist. Wen es interessiert: Hier gibt es eine Einladung. Das trifft besonders zu, wenn man erst im Nachhinein merkt, dass die View scrollbar sein muss (wenn man feststellt, dass Teile der View hinter der Tastatur verschwinden).

Der folgende Code fügt eine UIScrollView zur Laufzeit hinzu und löst alle damit verbundenen Sorgen. Diesen Code sollte man der UIViewController-Basisklasse hinzufügen, die für alle Controller gilt, die vertikal scrollbar sein sollen.

Im Interface Builder oder Xamarin iOS Designer muss man dann nur noch sicherstellen, dass es nicht nur Constraints für die vertikale Position aller Elemente gibt, sondern dass es noch ein Constraint gibt, das den Abstand zwischen der Unterkante des untersten Elements und der Unterkante der View des View Controllers gibt. Hierdurch wird die vertikale Größe des Inhalts der Scroll View eindeutig definiert.

Serielle Kommunikation mit Reactive Extensions

Der Haupteinsatzzweck von Reactive Extensions (Rx) ist die Verarbeitung von Event-Streams. Ich habe Reactive Extensions eingesetzt, um Daten auf der seriellen Schnittstelle zu empfangen und habe dabei einige neue Dinge gelernt.

Hier ist der Code, der Observable.FromEventPattern<T>() verwendet, um aus dem .NET-Event SerialPort.DataReceivedEvent ein IObservable<T> zu erzeugen:

Das Event enthält leider keinerlei Informationen zu den empfangenen Daten, es signalisiert nur, dass neue Daten vorliegen. Das Auslesen der Daten erfolgt im Lambdaausdruck. Das Auslesen der seriellen Schnittstelle liefert eine Menge an Bytes. Diese Menge könnte genau eine Nachricht enthalten oder nur einen Teil einer Nachricht oder mehrere Nachrichten. Aus diesem Grund sollte das Observable ein IObservable<byte> sein, d.h. einen rohen Strom an Bytes erzeugen ohne einen Hinweis darauf, wo eine Nachricht anfängt oder aufhört. Dies wird erreicht über die Extension Method public static IObservable<TResult> SelectMany<TSource, TResult>(this IObservable<TSource> source, Func<TSource, IEnumerable<TResult>> selector), die verwendet wird, um die vom Lambdaausdruck zurückgegebenen Bytes in ein IObservable<byte> zu transferieren.

Zu diesem Zeitpunkt habe ich einen Strom an Bytes. Diese Bytes sollten idealerweise in Nachrichten gruppiert werden. Bei dem von mir eingesetzten Protokoll werden Nachrichten durch ein besonderes Byte von einander getrennt. Für die Aufteilung gibt es zwei mögliche Ansätze:

In diesem Beispiel wir ein neues Observable mit Hilfe von Observable.Create() erzeugt. Dieses Observable abonniert sich auf den Bytestrom, sammelt die Daten in einer lokalen Liste und feuert OnNext() sobald ein Nachrichtentrenner entdeckt wurde.

Diese Version verwendet den Scan()-Operator, um das gleiche zu erreichen. Die Ausgabe ist ein IObservable<IEnumerable<byte>>, der für jede neue Nachricht einIEnumerable<byte> feuert.

Dieser Code funktionierte sehr gut bis zu dem Zeitpunkt, als ich mehrere Observer an den Stream hängte: Einen zur Verarbeitung der Nachrichten und einen zur Ausgabe der Nachrichten auf der Debugkonsole. Hierdurch wurden die seriellen Daten immer nur von einem Subscriber gelesen, nicht von allen. Hierfür gibt es zwei mögliche Lösungen: Man kann ein Subject<IEnumerable<byte>> einführen, das sich auf serialPortSource abonniert und auf das sich alle Konsumenten abonnieren. Alternative kann man den Publish()-Operator verwenden, der diese Arbeit übernimmt.

Aus dem neuen Observable, das Listen an Bytes produziert, lässt sich leicht mit Hilfe des Select()-Operators ein Observable erzeugen, das deserialisierte Nachrichten produziert.

Jetzt bleibt noch die Frage, wie man die empfangenen Daten in einem typischen Workflow verwendet: Das Senden einer Nachrichten und Empfangen der Antwort. Hier ist ein Beispiel:

Dieses Beispiel verwendet den Replay()-Operator. Replay sammelt alle Events, die das Observable nach Aufruf von Connect() feuert. Nach dem Aufruf von Connect() wird eine serielle Nachricht an das Gerät am anderen Ende der seriellen Leitung gesendet. Der zweite Aufruf von await filtert die eingehenden Nachrichten nach der erwünschten Nachrichten (und verwendet dabei ein Kriterium, das vor dem Aufruf noch gar nicht bekannt war), fügt ein Timeout hinzu, verwendet FirstAsync(), um ein Observable zu erzeugen, das nur das erste Element gefolgt von OnCompleted() zurückgibt und wartet dann auf das OnCompleted() mit Hilfe von await. Da Replay() alle Nachrichten aufzeichnet, werden beim folgenden await-Aufruf alle Nachrichten vom Gegenüber berücksichtigt, egal ob diese vor oder nach dem zweiten await-Aufruf eintrifft.

ALM Days 2015: Mobile in the Enterprise

Am 11. und 12. März 2015 fanden die ALM Days in Düsseldorf statt. Erstmals gab es im Rahmen der Veranstaltung einen Mobile Track. Mein Vortrag hatte den Titel Mobile in the Enterprise und handelte von den Herausforderungen, eine App für den Enterprise-Einsatz zu entwickeln. Das Video ist auf Channel 9 verfügbar, und die Slides liegen auf Azure.

Die Videos hat Dariusz in seinem Blog zusammengefasst.

Als Abschlussveranstaltung im Track gab es noch eine Podiumsdiskussion zum Thema „Cross-Platform-Entwicklungsstrategien“ mit Dariusz, Jörg, Daniel und mir.