Silverlight Tipp der Woche: async-basierte Silverlight Web-Services

by St. Lange 25. Mai 2012 21:10

Silverlight Tipp der Woche: async basierte Silverlight Web-Services

In diesem Tipp geht es um die Verwendung vom neuen C# 5 Schlüsselwort async beim Aufruf von Web-Services in Silverlight.

Zusammenfassung

async und await machen asynchronen Code wesentlich übersichtlicher, weil sie dessen logische Struktur erhalten. Das hilft vor allem beim Aufruf von Web-Services. In Silverlight sind Web-Service Referenzen von Hause aus aber leider nicht „awaitable“. Hier wird erklärt, wie es trotzdem geht.

(Dieser Artikel setzt die Arbeitsweise von async und await als bekannt voraus.)

Aufruf von asynchronem Code

Wer schon mal mit den neuen C# 5 Schlüsselworten async und await programmiert hat, fragt sich sehr schnell, wie er denn bisher ohne diese leben konnte.

In Visual Studio 2010 werden sie durch die Async CTP Version 3 für alle Arten von .NET Projekten zur Verfügung gestellt.

In Visual Studio 11 sind sie als Compiler-Feature bereits enthalten und in .NET 4.5 Projekten auch unmittelbar verfügbar. Für .NET 4 und Silverlight 5 Projekte muss zunächst noch das Microsoft.CompilerServices.AsyncTargetingPack via NuGet dazu installiert werden.

Vor allem beim Aufruf von asynchronen Web-Service-Funktionen in Silverlight kommt die neue Schreibweise wie gerufen. Im Prinzip könnte man jetzt folgendes schreiben:

var result1 = await client.DoThisAsync("Hallo");
var result2 = await client.DoThatAsync(result1, 43);

Keine Callbacks, keine Lambdas, kein Spaghetti-Code mit Error und UserState Properties. Nur zwei intuitive Zeilen Code, die exakt das machen, was man unmittelbar beim Lesen des Codes erwartet. Das ist schon phantastisch. Leider steht diese Schreibweise für Web-Services nicht unmittelbar zur Verfügung.

Das Problem

Die bisher generierten asynchronen Web-Service-Funktionen kann man natürlich nicht mit async verwenden, denn Funktionen, die „awaitable“ sein wollen, müssen ein Task- oder Task<T>-Objekt zurückgeben. Für bestehende Klassen wie WebClient gibt es daher Extension-Methods wie DownloadStringTaskAsync als Variante von DownloadStringAsync, die ein Task<string> zurückliefert.

Beim Import von Service-Referenzen müssten also eigentlich nur anders aufgebaute Funktionen generiert werden. Diese Funktionen müssten Task-Objekte zurückgeben und dafür würden die ganzen Completed-Handler und deren EventArgs wegfallen. Für .NET 4.5 Projekte wurde dazu in Visual Studio 11 eine neue Option beim Importieren eingeführt: „Generate task-based operations“. Diese Option generiert alternative Funktionen, die man mit await verwenden kann. Für Silverlight oder .NET Projekte kleiner als 4.5 ist diese Option allerdings nicht verfügbar. Hier der Sceenshot für ein Silverlight 5 Projekt:

Die Option ist grau. Und in Visual Studio 2010 geht das natürlich sowieso nicht.

Die Lösung

Beim Importieren eines Web-Services generiert einem der Compiler ja schon asynchrone Funktionen mit jeweils einem Completed-Handler. Vielleicht kann man den Code ja irgendwie wiederverwenden. Bei meinem Versuch die generierten Funktionen mit Hilfe eines Expression-Trees zu zerlegen, um dann mit Reflection die Funktionen des darunterliegenden Channels aufzurufen, habe ich eine sehr viel einfachere Lösung gefunden.

Nehmen wir zur Erläuterung einen WCF-Webservice mit drei typischen Funktionen:

public void DoAction(string s, double d, DateTime t) …
public string DoThis(string s) …
public int DoThat(string s, int n) …

Dieser Service wird wie üblich in einer Web-Anwendung implementiert und über „Add Service Reference“ zum Silverlight Projekt unter dem Namen Service1 hinzugefügt. Der Compiler generiert daraus diverse Dateien, die man auch sehen kann, wenn für das Projekt „Show All Files“ aktiv ist. Eine der generierten Dateien ist Reference.cs, die wiederum u.a. die Klasse Service1Client enthält. Service1Client enthält die private Unterklasse Service1ClientChannel, eine typsichere Implementierung des Servicecontracts unseres Webservices. Und hier können wir ansetzen, denn der Servicecontract ist auf Basis des BeginInvoke/EndInvoke Patterns implementiert. Dieses Pattern wurde bereits in .NET 1.0 eingeführt und benötigt für jede asynchrone Operation zwei Funktionen, die als Paar immer den Namenskonventionen BeginXxx und EndXxx folgen. BeginXxx liefert ein IAsyncResult zur Überwachung der asynchronen Operation und EndXxx nimmt nach deren Beendigung ein IAsyncResult entgegen und extrahiert daraus den Rückgabewert. Die wesentlich neuere Klasse Task verfügt über eine Kompatibilitätsmethode FromAsync, die asynchrone Operationen, die über ein solches async-Pattern bereitgestellt werden, in ein Task-Objekt umwandelt.

Da Service1Client freundlicherweise als partial deklariert ist, brauchen wir die Klasse nur um drei neue Funktionen in der gewünschten Form ergänzen:

public partial class Service1Client
{
  public Task DoActionTaskAsync(string s, double d, DateTime t)
  {
    return Task.Factory.FromAsync(Channel.BeginDoAction(s, d, t, null, null), Channel.EndDoAction);
  }
 
  public Task<string> DoThisTaskAsync(string s)
  {
    return Task<string>.Factory.FromAsync(Channel.BeginDoThis(s, null, null), Channel.EndDoThis);
  }
 
  public Task<int> DoThatTaskAsync(string s, int n)
  {
    return Task<int>.Factory.FromAsync(Channel.BeginDoThat(s, n, null, null), Channel.EndDoThat);
  }
}

Das Muster ist leicht zu erkennen. In die Funktion FromAsync werden das Ergebnis des Aufrufs der BeginXxx-Funktion sowie ein Delegate auf EndXxx reingereicht. Die beiden letzten Parameter der BeginXxx-Funktionen sind übrigens immer ein AsyncCallback und ein User-State Objekt. Beide sind null, da wir sie hier nicht benötigen.

Da alle Servicefunktionen bereits unter den Namen XxxAsync existieren, habe ich sie analog zu oben erwähnten Extension-Methods von WebClient in XxxTaskAsync umbenannt. Alternativ könnte man auch eine neue Klasse von Service1Client ableiten und die bisherigen Funktionsnamen mit new überschreiben. Das Ganze mit Extention Methods zu machen funktioniert hingegen nicht, weil die Property Channel protected ist.

Mit verhältnismäßig wenig Tipparbeit können wir jetzt doch unseren Code von oben schreiben:

var result1 = await client.DoThisTaskAsync("Hallo");
var result2 = await client.DoThatTaskAsync(result1, 43);

Die zweite Funktion wird mit dem Ergebnis der ersten aufgerufen, ganz natürlich und ohne Klimmzüge.

Das hintereinander Aufrufen von asynchronen Funktionen ist aber nur die Spitze des Eisbergs. Auch der folgende (hier inhaltlich unsinnige) Code ist möglich:

string result = null;
try
{
  var client = new Service1Client();
  if (await client.DoThatTaskAsync("xxx", 43) > 123)
    result = await client.DoThisTaskAsync("Hallo");
  else
  {
    await client.DoActionTaskAsync("yyy", 4.5, DateTime.Now);
    int x = 321;
    while ((x = await client.DoThatTaskAsync("zzz", x)) < 42)
      result += await client.DoThisTaskAsync(x.ToString("0"));
  }
}
catch (Exception ex)
{
  Debug.WriteLine(ex.Message);
}

Ein try-Block um asynchrone Funktionen, die sich innerhalb beliebiger Kontrollstrukturen befinden! Und eine Exception auf dem Server landet stets sauber beim Client im catch-Block.

Wer immer noch nicht von async/await restlos begeistert ist, sollte mal versuchen, den Code oben mit den bisherigen Mitteln zu schreiben. Insbesondere wegen des try-catch-Blocks ist dies eine sehr schwierige Übung.

Noch ein technischer Hinweis: Das Erzeugen des Task-Objektes führt zur Erzeugung eines zusätzlichen Threads, der auf das WaitHandle von IAsyncResult wartet. Dies hat aber praktisch keinerlei Laufzeitrelevanz. Der Thread wird einmal im Threadpool erzeugt und dann wiederverwendet. Die überwiegende Zeit wartet er auf die Fertigstellung der asynchronen Operation. Danach benachrichtigt er den Main-Thread, damit dieser hinter dem await weitermacht.

Fazit

„Fast und fluid“ war Silverlight ja immer schon. Mit async/await und den selbst gekapselten Task-basierten Service-Operationen haben wir nun auch in Silverlight, was mit der Windows Runtime der neue Standard werden wird. Und es funktioniert so gut, dass ich schon fast vergessen habe, wie ich es bisher ohne await gemacht habe.

Hier der Beispielcode zum Ausprobieren:

TaskBasedWebOperations.zip (61 kB)

Tags: , ,

Silverlight

Windows 8 und die Zukunft von Silverlight

by St. Lange 27. September 2011 17:30

Der Nachfolger von Windows 7

Zunächst einmal wird auf Windows 8 alles laufen, was auch auf der Vorgängerversion läuft. Es gab ja einige Befürchtungen, dies könnte nicht so sein.

Neben vielen wirklich coolen Neuerungen wie Hyper-V auf dem Client, RemoteFX oder Windows To Go, ist die neue Oberfläche im Metro Design und die ihr zugrundeliegende Windows Runtime das innovative Highlight von Windows 8. Neben dem gewohnten Windows Desktop, der auch weiterhin zur Verfügung stehen wird, gibt es die neue Metro Oberfläche, die speziell auch Touch-Geräte unterstützt und teilweise schon vom Windows Phone bekannt ist.

Grundlegend neu ist die Windows Runtime, die die Programmierschnittstelle für Metro-Apps bereitstellt und technologisch neue Wege einschlägt.

Die Windows Runtime

Die schlicht Windows Runtime (WinRT) genannte Schnittstelle zum Betriebssystem ist ein ABI (Application Binary Interface) nach dem Vorbild von COM. Die WinRT ist in nativem Code mit C++ geschrieben und darauf ausgelegt, das UI „fast and fluid“ zu machen. Um sie zu programmieren, wurde C++ mit vielen neuen Features angereichert, die man aus .NET kennt: Properties, Exceptions, Delegates, Events, Generics, Lambda-Expression, u.a. Die dazugehörige Syntax ist praktisch identisch mit C++/CLI, nur wird eben kein managed, sondern nativer Code generiert. Die WinRT stellt ihre Funktionalität in Form von Komponenten, die Interfaces implementieren, zur Verfügung. Um aus verschiedenen Programmiersprachen heraus genutzt werden zu können, legt sie bestimmte Datentypen und Konventionen fest. Das ist ganz ähnlich wie die CLR in .NET funktioniert, aber die WinRT basiert nicht auf .NET. Sie verwendet insbesondere kein Garbage-Collection, sondern das aus COM bekannte Reference-Counting. Um die Programmierung zu vereinfachen wurde dazu in C++ ein neuer new-Operator eingeführt, der den Compiler veranlasst, die Aufrufe von AddRef und Release selbst zu generieren, wenn der Pointer auf ein so erzeugtes Objekt verwendet wird.

Die in COM verwendete IDL (Interface Definition Language) zur Beschreibung von Schnittstellen wird nicht eingesetzt. Stattdessen kommt ein moderneres Metadatenformat zum Einsatz, welches eine Weiterentwicklung des aus .NET bekannten Metaformats darstellt. Aufgrund dieser Windows Metadata (WinMD) kann die WinRT von beliebigen Sprachen aus anprogrammiert werden (im Gegensatz zu .NET, was nur von beliebigen managed Sprachen aus angesprochen werden kann). Zurzeit sind C++ und JavaScript die einzigen unmanaged Sprachen und C# und VB die einzigen managed Code Sprachen, die zur Verfügung gestellt werden.

Das Binden an eine konkrete Programmiersprache wird als „Projection“ bezeichnet, wohl um auszudrücken, dass es mehr ist als simples „Binding“. Da die WinRT sich über ihre Metadaten selbst beschreibt, ist die Projection in eine konkrete Sprache automatisiert und sehr effizient möglich. Sprachen wie C# können diese Metadaten praktisch direkt nutzen und benötigen daher die in COM notwendigen Interop-Assemblies nicht mehr.

Die Projection erlaubt außerdem eine tiefe Integration in die jeweilige Programmiersprache. So sind beispielsweise ca. 10 bis 15 Prozent der WinRT Funktionen asynchron, d.h. sie starten einen länger andauernden Vorgang und liefern gleichzeitig ein Interface auf ein Operation-Objekt zurück, über das man diesen Vorgang kontrollieren und am Ende dessen Ergebnis abfragen kann. Ruft man eine solche Funktion aus C# heraus aus, kann man direkt das neuen Schlüsselwort await aus dem async/await Pattern von C# 5 verwenden. Aus dem IAsyncOperation der WinRT wird ein Task<T> in C#. In JavaScript dagegen wird die gleiche WinRT-Funktion in ein entsprechendes Promise-Konstrukt umgewandelt.

Programmiermodell

Das folgende Schaubild ist eine der Kernfolien von Microsoft.

Es zeigt deutlich, dass die Metro Apps neben dem bisherigen Windows Desktop Apps liegen, sie also um etwas Neues ergänzen und nichts ersetzen.

Die von der WinRT angebotenen Klassen und Funktionen sehen Silverlight zum Verwechseln ähnlich und jeder Silverlight- bzw. WPF-Programmierer findet sich praktisch sofort zurecht. Zwar sind die Namespaces und viele Details etwas anders, aber die aus Silverlight bekannten Controls und Konzepte wie XAML, Resources, Styles, Templates, etc. werden alle nahezu identisch unterstützt. Auch das Resource-Format .resw ist praktisch das Gleiche wie .resx.

Die Konzepte der WinRT eignen sich hervorragend für alle Arten von Schnittstellen, stehen aber zunächst mal nur unter Windows 8 und nur für Metro Apps zur Verfügung.

C++/CX

C++/CX ist der Name des für die WinRT erweiterten C++. Stark vereinfacht kann man es so ausdrücken: C++ plus die coolen Sprachfeatures von C# mit Reference Counting anstatt Garbage Collection. Da C++/CX nativen Code erzeugt, kann man nun die unschlagbare Performance von C++ mit den modernen Sprachmitteln von C# nutzen. Ein gewaltiger Schritt für C++ als Basissprache moderner Betriebssysteme. Microsoft möchte, dass C++/CX in den ANSI-Standard einfließt. Zurzeit kann man C++/CX allerdings nur unter Windows 8 und nur für WinRT Metro Apps verwenden.

JavaScript

Auch für JavaScript ist die WinRT verfügbar. Dadurch sind mit HTML5 Browser-basierte Metro-Anwendungen möglich, die alle Features des Betriebssystems nutzen können. Ich sehe zwei große Vorteile dieses Ansatzes. Zum einen findet das riesige Heer von JavaScript-Programmierern nun einen einfachen Zugang zur Programmierung von Windows Apps. Ein kluger Schachzug von Microsoft, der auch von Microsofthassern nur schwer zu kritisieren ist. Zum anderen lässt sich dynamischer Content aus dem Web leichter in eine HTML5- als in eine XAML-basierte Metro App integrieren.

Bevor nun jemand wegen HTML5 auf falsche Gedanken kommt: Alle Metro Apps basieren auf der WinRT. HTML5 Apps werden zwar vom IE 10 gehostet, laufen aber ausschließlich auf Windows 8 Geräten und sind damit genauso proprietär wie eine XAML-basierte Metro App.

C#/VB

Als .NET Sprachen stehen zurzeit C# und VB zur Verfügung. Auch wenn das obige Schaubild den Eindruck erweckt, als würde C# irgendwie ohne .NET Framework laufen, gibt es genau wie bisher eine CLR, Garbage Collection, IL Code und ein .NET Framework. Tatsächlich ist auf Windows 8 auch nur ein .NET 4.5 Framework installiert, mit dem man wahlweise WinForms, ASP.NET, WPF, etc. Anwendungen erstellen kann oder alternativ auf Basis der WinRT Metro Apps. Eine Vermischung der beiden Welten innerhalb einer Anwendung ist nicht vorgesehen.

Ansonsten hat man das Gefühl, Silverlight oder WPF zu programmieren. XAML, Controls, Events, Styles, Templates, ResourceDictionaries, VisualStateManager, usw. sind bis ins Detail sofort vertraut. Natürlich gibt es etliche Unterschiede, beispielsweise bei den Touch-Events oder bei neuen Namensräumen, aber jeder Silverlight-Entwickler findet sich schnell zurecht.

Es ist auch möglich, mit C#/VB neue WinRT-kompatible Komponenten zu erstellen, wenn man sich an bestimmte Konventionen hält. So kann man beispielsweise nützliche Hilfsroutinen, sein ViewModel oder neue Controls in C# schreiben und diese dann in einer HTML5 App über JavaScript verwenden. So muss das auch sein. Ich bin absolut überzeugt, dass .NET die dominierende Technologie für WinRT Anwendungen werden wird, weil .NET einfach die produktivste Art ist, Software zu entwickeln, die bisher erfunden wurde.

Performance zählt

Eine der wichtigsten Neuerungen der WinRT sind drastische Verbesserungen der UI-Performance gemäß dem Metro-Paradigma „fast and fluid“. Am einfachsten lässt sich das anhand eines Beispiels verdeutlichen. In Silverlight beruhen Animationen letztlich darauf, dass man über eine Timeline gesteuert eine oder mehrere Dependency-Properties über einen Zeitraum hinweg verändert. Basierend auf diesen Änderungen rendert der UI-Thread die Oberfläche mehrmals pro Sekunde neu. Auf einem Intel Desktop-Prozessor ist dies kein Problem, wenn auch ein Blick in den Taskmanager zeigt, dass damit ein Prozessorkern oft zu 100% ausgelastet ist. Auf einem mobilen Gerät mit einem ARM Prozessor führt dies jedoch zu einem unlösbaren Problem. Die Animation wirkt entweder ruckelig oder benötigt bestenfalls einfach zu viel Energie und belastet damit die Batterie unangemessen. Dies war einer der Hauptgründe, warum Microsoft vor rund 3 Jahren Silverlight nicht auf Windows Mobile sinnvoll zum Laufen bekommen hat. Andererseits erwartet ein Anwender heutzutage, das beispielsweise ein zu löschendes Element aus einer Liste nicht einfach „wegpoppt“, sondern sich langsam ausblendet, während die dahinterliegenden Elemente nachrücken und die Lücke schließen. Grundsätzlich kein Problem für ein Silverlight FluidMoveBehavior, aber leider sind diese zu performancehungrig für mobile Geräte.

Die WinRT geht daher mit fest eingebauten Animationen für Standard-Controls einen neuen Weg. Für das obige Beispiel aktiviert man einfach die sog. Theme-Transition AddDelete aus der eingebauten Animations-Library. Etwas vereinfacht ausgedrückt rendert diese Animation alle beteiligten List-Items genau einmal und bewegt sie dann GPU-beschleunigt über das Display, ohne dass der Inhalt dabei neu berechnet werden müsste. Visuell macht dies keinerlei Unterschied, sieht geschmeidig aus und braucht nur einen Bruchteil der Energie. Die vorgefertigten Animationen sind vielfältig und lassen sich auch untereinander kombinieren. Wem das dennoch nicht ausreicht, kann die jetzt als „Dependent Animation“ bezeichneten bisherigen Animationen immer noch verwenden. Allerdings muss man sie explizit einschalten, damit einem auch bewusst wird, dass man hier Leistung verbrät.

Dieses Beispiel zeigt sehr deutlich, wie sehr die WinRT auf Geschwindigkeit, flüssiges UI und Energieeffizienz getrimmt ist. Der Verzicht auf Garbage-Collection in der WinRT erfolgte übrigens nicht aufgrund von Performanceüberlegungen. Zum einen wollte man auf Betriebssystemebene die vollständige Kontrolle über die Speicherverwaltung haben und zum anderen sollte die WinRT wirklich von jeder Programmiersprache aus genutzt werden können.

Weitere Programmiersprachen

C++, JavaScript, C# und VB sind zurzeit die einzigen verfügbaren Programmiersprachen für die Windows Runtime und man kann damit auch nur Metro Apps bauen. Da jedoch alle Entwickler, mit denen ich auf der BUILD gesprochen habe, völlig begeistert sind von der WinRT, vermute ich mal, dass in naher Zukunft diverse Programmiersprachen auf die WinRT aufgesetzt werden. Einfach so, weil es eine interessante Herausforderung ist und Entwickler gerne spannende neue Dinge machen. Außerdem lassen sich viele Sprachen vermutlich sogar leichter auf die WinRT aufsetzen als auf das .NET Framework.

Silverlight

Zur strategischen Zukunft von Silverlight wurde auf der BUILD nichts gesagt. Natürlich läuft Silverlight im Browser und Out-of-Browser auf dem Windows 8 Desktop. Das war aber sowieso klar, da schon immer jede neue Windows-Version die Software der Vorgängerversion ausführen konnte. Auch das Silverlight, Flash und sonstige PlugIns nicht innerhalb einer HTML5 Metro App laufen werden ist logisch. Metro Apps laufen in einer Sandbox und werden vor dem Verkauf über den Windows Marketplace von Microsoft verifiziert. Und zu diesem Konzept passen nun mal keine Browser-PlugIns, auch nicht Silverlight.

Nun ist Metro zwar technisch gesehen eine Weiterentwicklung von Konzepten aus Silverlight, deckt aber einen größtenteils anderen bzw. neuen Markt ab. Metro steht für eine neue UX auf Windows 8 Geräten und läuft weder auf Windows XP, Vista oder 7, noch auf dem Desktop weder im Browser noch Out-of-Browser, noch auf dem Mac. Damit ist es auch mittelfristig keine Alternative für Projekte, die man heute mit Silverlight machen würde. Silverlight hat sich von der aus heutiger Sicht nicht mehr realistischen „WPF/Everywhere“ Idee hin zu einer Browser-basierten Windows Desktop Technologie entwickelt. Durch die Einführung von PInvoke kann eine Full-Trust Silverlight 5 Anwendung sowohl im Browser als auch Out-of-Browser alles machen, was mit Win32 möglich ist. Das ist eine andere Richtung als die von Metro eingeschlagene. Metro erweitert Windows für neue Gerätetypen, ersetzt aber keine der bisherigen Desktop-Technologien.

Ich vermute, dass Microsoft selbst noch nicht so genau weiß bzw. entschieden hat, was nach Silverlight 5 kommen wird. Viele Firmen haben auf Silverlight gesetzt und auch LightSwitch basiert darauf. Daher bin ich sicher, dass Silverlight auch in den nächsten Jahren eine zunehmend wichtigere Rolle spielen wird. Für reine Web-Anwendungen mit maximaler Reichweite muss man ausschließlich auf HTML und JavaScript setzen, das war aber auch schon bei Einführung von Silverlight so und Silverlight war nie als Alternative zu HTML angedacht. In Szenarien, wo man jedoch die Wahl zwischen HTML oder Silverlight hat, ist und bleibt Silverlight fast immer die bessere Alternative.

Die tägliche Arbeit

Metro und die WinRT sind sowohl von der UX Seite als auch technologisch ein großer Schritt nach vorne. Die Zukunft wird sicherlich sehr spannend werden. Bis Windows 8 auf dem Markt ist und eine nennenswerte Verbreitung hat, wird allerdings noch eine ganze Weile vergehen. Wer also nächsten Monat ein neues Projekt startet und sich heute Gedanken über die Technologie macht, für den hat sich durch die BUILD nicht viel geändert. Er sollte Silverlight oder WPF für den Desktop verwenden, genau wie bisher. Zwei sehr positive Dinge für die unmittelbare Zukunft hat die BUILD allerdings schon gebracht: Zum einen weiß jetzt jeder Silverlight-Entwickler, dass er sein Know-how zur UI-Programmierung, XAML oder RIA Services auch langfristig mit Metro weiter nutzen kann. Zum anderen sind die von Microsoft durch eine unglückliche Präsentation von Windows 8 im Juni geschürten Befürchtungen, die .NET Entwicklung würde durch JavaScript ersetzt, endlich vom Tisch.

Ich vermute, dass die WinRT langfristig das Win32 API verdrängen wird, jedoch frühestens in der übernächsten Windows-Version. Vorher werden aber bestehende Systeme wie Windows Phone 8 die WinRT als Basis erhalten. Microsoft hat jedenfalls eine sehr durchdachte und innovative Grundlage für die nächsten Jahre geschaffen.

Wenn ich mir was wünschen könnte: Silverlight 6 im Metro Look mit einer WinRT als PlugIn für weitere non-Microsoft Plattformen. Dies wird aber wohl ein Wunsch bleiben.

Tags: , ,

.net | Silverlight

DataTemplate Selector für Silverlight

by St. Lange 9. Oktober 2009 21:09

Data Templates sind eine feine Sache. Sie erlauben es, die Präsentation von Daten sehr einfach und präzise zu definieren. Hierzu vorab ein einfaches Beispiel in Form einer Silverlight Listbox, die die 16 deutschen Bundesländer auflistet. Standardmäßig ist das Data Template für die Items einer Listbox einfach ein ContentPresenter. Dieser ruft an den Items die Funktion ToString auf und stellt den resultierenden Text dar. Durch das Zuweisen eines Data Templates wird aus dem simplen String eine viel informativere Darstellung.

Hier kann man das Beispiel ausprobieren

In diesem Beispiel wird beim Drücken von „Apply Template“ ein Data Template geladen und an die Property ItemTemplate der Listbox zugewiesen. Dies hat zur Folge, dass die Listbox zur Darstellung ihrer Items nicht mehr einfach nur den Default Content Presenter verwendet, sondern ein individuell auf die Daten zugeschnittenes Template. Diese Funktionalität ist in WPF und Silverlight nahezu identisch.

WPF bietet nun zusätzlich noch die Möglichkeit, das Template abhängig von Daten Items individuell auszuwählen. Dazu muss man eine eigene Klasse MyTemplateSelector von DataTemplateSelector ableiten und darin die Funktion SelectTemplate überschreiben. Eine Listbox hat wie jedes Items Control in WPF die Property ItemTemplateSelector. Dieser Property muss man MyTemplateSelector zuweisen. Die Listbox ruft nun für jedes Daten Item was sie in Ihrer Items Source findet die Funktion SelectTemplate in MyTemplateSelector auf. So hat man die Möglichkeit, im Code abhängig von den Daten jedes einzelnen Items unterschiedliche Templates auszuwählen.

Lösung in Silverlight

Silverlight 3 kennt diese WPF Funktionalität nicht, aber man kann sich eine vergleichbare Funktionalität einfach selber bauen. Da Silverlight für alle Daten Items immer das gleiche Template verwendet, muss man einen Trick anwenden: Man baut sich ein spezielles Data Template, welches genau nur ein Content Control beinhaltet. In diesem Content Control redefiniert man die Funktion OnContentChanged und wechselt hier einfach das Data Template des Content Controls abhängig vom aktuellen Content. Diese zusätzliche Indirektion liefert im Ergebnis das gleiche wie in WPF.

Ein Beispiel soll zeigen, wie es im Einzelnen funktioniert. Um es einfach zu halten, bauen wir das obige Beispiel mit den Bundesländern so um, dass alle Bundesländer, die Stadtstaaten sind, mit einem anderen Template dargestellt werden sollen.

Zunächst die Klasse DataTemplateSelector für Silverlight:

/// <summary>
/// Base class for Silverlight DataTemplateSelector.
/// </summary>
public class DataTemplateSelector : ContentControl
{
  /// <summary>
  /// Called when the value of the <see cref="System.Windows.Controls.ContentControl.Content"/> property changes.
  /// </summary>
  protected override void OnContentChanged(object oldContent, object newContent)
  {
    base.OnContentChanged(oldContent, newContent);

    var template = SelectTemplate(newContent, null);
    if (template != null)
      ContentTemplate = template;
  }

  /// <summary>
  /// When overridden in a derived class, returns a DataTemplate based on custom logic.
  /// </summary>
  /// <param name="item">The data object for which to select the template. </param>
  /// <param name="notUsedInSilverlight">The not used in silverlight. Exists for WPF compatibility only.</param>
  /// <returns>Returns a DataTemplate or a null reference. The default value is a null reference.</returns>
  public virtual DataTemplate SelectTemplate(object item, DependencyObject notUsedInSilverlight)
  {
    return null;
  }
}

Wie man sieht, ist diese Klasse von ContentControl abgeleitet. Ändert sich der Content, wird über die Funktion OnContentChanged die Funktion SelectTemplate aufgerufen. Diese Funktion hat die gleiche Signatur wie ihr WPF Gegenstück.

Um unser Beispiel umzusetzen wird eine neue Klasse MyTemplateSelector von DataTemplateSelector abgeleitet:

public class MyTemplateSelector : DataTemplateSelector
{
  public override DataTemplate SelectTemplate(object item, DependencyObject container)
  {
    var stateInfo = item as States.StateInfo;
    if (stateInfo != null)
    {
      if (stateInfo.Capital != "–")  // the state is not one of the three German city-states
      {
        // This variation demonstrates how to read the template from a resource file
        var info = Application.GetResourceStream(new Uri("/DataTemplateSelectorSample;component/Assets/RegularStateTemplate.xaml", UriKind.Relative));
        using (var reader = new StreamReader(info.Stream))
        {
          return XamlReader.Load(reader.ReadToEnd()) as DataTemplate;
        }
      }

      // This variation demonstrates how to read the template from the Application resources
      return Application.Current.Resources["CityStateTemplate"] as DataTemplate;
    }
    return null;
  }
}

Anhand der Property Capital wird unterschieden, ob es sich bei einem Daten Item um ein Stadtstaat oder ein normales Bundesland handelt. Je nachdem was es ist, werden unterschiedliche Templates geladen.

Zuletzt muss noch die Listvox mit der Klasse MyTemplateSelector verknüpft werden:

<ListBox x:Name="lbxStatesOfGermany" Margin="10 10 10 10">
  <ListBox.ItemTemplate>
    <DataTemplate>
      <tmpl:MyTemplateSelector Content="{Binding}"/>
    </DataTemplate>
  </ListBox.ItemTemplate>
</ListBox>

Wie bereits gesagt, wird MyTemplateSelector zum Item Template der Listbox. Die Listbox erzeugt nun für jedes ihrer Daten Items eine Instanz von MyTemplateSelector und bindet das jeweilige Item daran. Abhängig vom Item setzt nun jede Instanz von MyTemplateSelector wiederum ihr eigenes Content Template.

Hier das Ergebnis zum ausprobieren

Fazit

Der Silverlight Data Template Selector lässt sich genau so einfach verwenden wie in WPF. Er funktioniert natürlich nicht nur mit Listboxen, sondern mit alle Items Controls. Dieses Beispiel zeigt wieder einmal, wie einfach und elegant man Silverlight erweitern kann.

Hier der gesamte Quellcode der beiden Beispiele zum Downloaden:

SilverlightDataTemplateSelector.zip (202,00 kb)

Tags: ,

Silverlight | WPF

Web Services in Silverlight synchron aufrufen

by St. Lange 28. September 2009 21:18

Einen Web Service aus Silverlight heraus aufzurufen ist sehr einfach. Man importiert den Service über einen Wizard und Visual Studio generiert alle notwendigen Dateien und Funktionen. Im Gegensatz zum normalen .net Framework können die Service Funktionen aber nur asynchron aufgerufen werden. So wird verhindert, dass ein wirklich sehr unerfahrener Programmierer eine Service Funktion direkt aus dem UI Thread synchron aufruft und damit nicht nur seine Silverlight Anwendung, sondern den gesamten Browser für die Zeit des Funktionsaufrufes einfriert. An sich ja gut gemeint, aber bei ernsthaften Anwendungen ist das Fehlen von synchronen Service Aufrufen schon lästig.

Wenn man beispielsweise in einem Background Thread mehrere Web Service Aufrufe nacheinander oder in einer Schleife ausführen möchte, ist dies ebenfalls nur asynchron möglich. Man muss also in so einem Fall überflüssigerweise mit Completed Handlern arbeiten, obwohl ein synchroner Aufruf hier nicht nur ungefährlich,  sondern genau richtig wäre. Hier würde ein synchroner Aufruf zu sequenziellem und übersichtlicherem Code führen.

Wenn man sich mit Web Services gut auskennt, kann man natürlich über eine ChannelFactory seine synchronen Aufrufe selber bauen, wie es beispielsweise in dem hervorragenden Artikel von Daniel Vaughan beschrieben ist. Dies setzt aber ein tieferes Verständnis von der Architektur von Web Services voraus, das man sich genau jetzt nicht aneignen will, weil man ja gerade mit seiner ersten großen Silverlight Anwendung genug zu tun hat.

Der vom mir entwickelte einfache Lösungsweg zum synchronen Aufruf von Web Services besteht in der Klasse SyncCallHelper, die unmittelbar nachdem asynchronen Aufruf der Service Funktion den laufenden Thread so lange anhält, bis der asynchrone Aufruf beendet ist. Die Verwendung sieht beispielsweise so aus:

var syncHelper = new SyncCallHelper<SampleServiceReference.DoSomeWorkCompletedEventArgs>();
client.DoSomeWorkCompleted += syncHelper.OnCompleted;
client.DoSomeWorkAsync(syncHelper);

if (syncHelper.Wait() && syncHelper.Succeeded)
{
  // success
  var result = syncHelper.EventArgs.Result;
}

SyncCallHelper blockiert in der Funktion Wait über ein ManualResetEvent den laufenden Thread so lange, bis die Service Funktion fertig ist und OnCompleted aufgerufen hat. Natürlich darf man diesen Code nur in einem Background Thread verwenden. Falls man ihn versehentlich doch mal aus dem UI Thread heraus aufruft, wird eine Exception ausgelöst. Im UI Thread würde der Code auch gar nicht funktionieren. Weil in diesem Fall Wait den UI Thread blockiert, wird der OnCompleted Handler niemals aufgerufen, da dieser in einem Synchronization Context auf den UI Threads wartet. Es läge also ein klassischer Deadlock vor.

Hier der Quellcode von SyncCallHelper:

namespace System.ComponentModel
{
  /// <summary>
  /// Helper class for synchronized asynchron WCF service calls.
  /// </summary>
  public class SyncCallHelper<T> where T : AsyncCompletedEventArgs
  {
    /// <summary>
    /// Initializes a new instance of the <see cref="SyncCallHelper<T>"/> class
    /// and sets the event semaphore to non-signaled.
    /// </summary>
    public SyncCallHelper()
    {
      Event = new ManualResetEvent(false);
    }

    /// <summary>
    /// Blocks the current thread until the current System.Threading.WaitHandle receives a signal.
    /// </summary>
    public bool Wait()
    {
      if (Deployment.Current.CheckAccess())
        throw new InvalidOperationException("SyncCallHelper must not be used in UI thread.");

      return Event.WaitOne();
    }

    /// <summary>
    /// Blocks the current thread until the current System.Threading.WaitHandle receives a signal,
    /// using 32-bit signed integer to measure the time interval.
    /// </summary>
    public bool Wait(int millisecondsTimeout)
    {
      if (Deployment.Current.CheckAccess())
        throw new InvalidOperationException("SyncCallHelper must not be used in UI thread.");

      return Event.WaitOne(millisecondsTimeout);
    }

    /// <summary>
    /// Blocks the current thread until the current instance receives a signal,
    /// using a System.TimeSpan to measure the time interval.
    /// </summary>
    public bool Wait(TimeSpan timeout)
    {
      if (Deployment.Current.CheckAccess())
        throw new InvalidOperationException("SyncCallHelper must not be used in UI thread.");
      return Event.WaitOne(timeout);
    }

    /// <summary>
    /// Gets a value indicating whether the service call is succeeded.
    /// </summary>
    public bool Succeeded
    {
      get { return EventArgs != null && EventArgs.Error == null && !EventArgs.Cancelled; }
    }

    /// <summary>
    /// Gets the event semaphore used to wait for completion of asynchronous WCF service calls.
    /// </summary>
    public ManualResetEvent Event { get; private set; }

    /// <summary>
    /// Gets the event args that contains the result of the WCF service call.
    /// Is null until the WCF service call has completed.
    /// </summary>
    public T EventArgs { get; private set; }

    /// <summary>
    /// Called when the WCF service call has completed.
    /// </summary>
    public void OnCompleted(object sender, T e)
    {
      var res = (SyncCallHelper<T>)e.UserState;
      res.EventArgs = e;
      res.Event.Set();
    }
  }
}

Übrigens kann man mit der Funktion Deployment.Current.CheckAccess auf einfache Weise überall im eigenen Code testen, ob man sich gerade im UI Thread oder in einem Background Thread befindet.

Hier nun noch ein Beispielprogramm, dass in einem Background Thread zwei Service Funktionen hintereinander aufruft:

void worker_DoWork(object sender, DoWorkEventArgs e)
{
  // This code is executed in background thread
  string result;

  var client = new SampleServiceClient();

  // Do 1st service call with no parameters
  var syncHelper1 = new SyncCallHelper<DoSomeWork1CompletedEventArgs>();
  client.DoSomeWork1Completed += syncHelper1.OnCompleted;
  client.DoSomeWork1Async(syncHelper1);

  if (syncHelper1.Wait() && syncHelper1.Succeeded)
  {
    result = syncHelper1.EventArgs.Result;
  }
  else
  {
    e.Result = "Error";
    return;
  }

  // Do 2nd service call with one parameter
  var syncHelper2 = new SyncCallHelper<DoSomeWork2CompletedEventArgs>();
  client.DoSomeWork2Completed += syncHelper2.OnCompleted;
  client.DoSomeWork2Async(42, syncHelper2);

  if (syncHelper2.Wait() && syncHelper2.Succeeded)
  {
    result += syncHelper2.EventArgs.Result;
  }
  else
  {
    e.Result = "Error";
    return;
  }

  e.Result = result;
}

Der Code läuft wie man sieht sequentiell ab und wird nicht durch OnCompleted Handler zerstückelt.

Noch ein wichtiger Hinweis: Im Beispielcode wird ein Background Worker direkt im Event Handler eines Button Clicks in der Code Behind Datei angelegt. So darf man ausschließlich in Beispielcode programmieren, der einen bestimmten Aspekt herausstellen soll!

Fazit

Microsoft möchte verhindern, dass man mit synchronen Web Service Aufrufen den Browser vorübergehend einfriert. Das ist absolut richtig und einen gewissen Schutz vor unsauberen Programmierpraktiken vorzusehen ist auch nicht verkehrt. Eine wesentliche bessere Lösung wäre es allerdings gewesen, die synchronen Web Service Aufrufe zusätzlich anzubieten und einfach eine InvalidOperationException auszulösen, wenn man sie aus dem UI Thread heraus versucht aufzurufen.

Hier der gesamte Quellcode des Beispiels zum Downloaden:

SynchronousWcfCalls.zip (40,99 kb)

Tags: ,

Silverlight

DataGrid mit Mausrad scrollen

by St. Lange 2. September 2009 10:55

Mit Silverlight 3 wird nun endlich das Mausrad unterstützt. Über das MouseWheel Event bzw. durch Überschreiben von OnMouseWheel kann eine Silverlight Anwendung auf das Drehen des Mausrads reagieren. Dies ist sehr viel einfacher als noch in Silverlight 2, wo die einzige Möglichkeit zur Nutzung des Mausrads in der Weiterleitung von Browserevents über Javascript in die Silverlight Anwendung bestand.

Die Silverlight Standard Controls von Microsoft unterstützen jedoch weiterhin das Scrolling per Mausrad nicht. Dafür gibt es meines Erachtens keinen sinnvollen Grund, zumal Microsoft das Mausrad erfunden hat! Vermutlich ist diese Funktionalität dem engen Zeitplan von Silverlight 3 zum Opfer gefallen – man hatte wahrscheinlich keine Zeit mehr die schon in Silverlight 2 enthaltenen Controls zu überarbeiten. Es gibt zwar eine neue Klasse ScrollViewerExtensions, mit deren Hilfe man einen ScrollViewer elegant um MouseWheel Funktionalitäten erweitern kann, dass hilft einem aber nicht, wenn man einfach nur sein DataGrid mit dem Mausrad scrollen möchte.

Da es schwierig ist, einen Kunden einerseits mit den Vorzügen von Silverlight zu begeistern und ihm andererseits zu erklären, das die DataGrids in seiner schönen neuen Silverlight Anwendung leider nicht mit dem Mausrad durchgeblättert werden können, hier ein einfacher Workaround.

Der Trick besteht darin, das DataGrid über seine Automation Schnittstelle zum Scrollen zu bringen. Mit der Funktion OnCreateAutomationPeer holt man sich einen IScrollProvider und scrollt damit das DataGrid im MouseWheel Event. Da OnCreateAutomationPeer protected ist, muss eine eigene Klasse MouseWheelDataGrid von DataGrid abgeleitet werden, um die Funktion überhaupt aufzurufen zu können. Übrigens lässt sich in Silverlight auch über Reflection eine protected Funktion nicht aufrufen (was auch korrekt ist). Man muss daher von der Klasse DataGrid ableiten.

Hier der Quellcode von MouseWheelDataGrid:

namespace System.Windows.Controls
{
  public enum ScrollMode
  {
    None, Small, Large
  }

  public class MouseWheelDataGrid : DataGrid
  {
    public ScrollMode ScrollMode { get; set; }

    protected override void OnMouseWheel(MouseWheelEventArgs e)
    {
      if (ScrollMode == ScrollMode.None)
        base.OnMouseWheel(e);
      else
      {
        e.Handled = true;
        bool scrollUp = e.Delta < 0;
        ((IScrollProvider)OnCreateAutomationPeer()).Scroll(ScrollAmount.NoAmount, ScrollMode == ScrollMode.Small ?
          (scrollUp ? ScrollAmount.SmallIncrement : ScrollAmount.SmallDecrement) :
          (scrollUp ? ScrollAmount.LargeIncrement : ScrollAmount.LargeDecrement));
      }
    }
  }
}

Tags:

Silverlight

PropertyChanged Event ohne Stringliteral aufrufen

by St. Lange 26. August 2009 17:43

Bei der Implementierung von INotifyPropertyChanged muss der Property-Setter das PropertyChanded Event auslösen, wenn sich der Wert der Property ändert. Das sieht beispielsweise so aus:

public class ViewModel : INotifyPropertyChanged
{
  public string Name
  {
    get { return _name; }
    set
    {
      if (_name != value)
      {
        _name = value;
        OnPropertyChanged("Name");
      }
    }
  }
  string _name;

  public event PropertyChangedEventHandler PropertyChanged;

  protected void OnPropertyChanged(string propertyName)
  {
    if (PropertyChanged != null)
      PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
  }
}

Den Namen der geänderten Property als Stringliteral an den Event Handler zu übergeben verhindert, dass er etwa beim Refactoring automatisch mit umbenannt wird. Ein möglicher Lösungsansatz, über den auch schon viel geschrieben wurde, besteht in Verwendung einer Lambda Expression:

OnPropertyChanged(x => x.Name);

Anstatt des Strings wird ein Expression Tree übergeben, aus dem dann der Name der Property ermittelt wird. Eine Implementierung dazu findet sich beispielsweise hier: http://www.lieser-online.de/blog/?p=130

Damit der Compiler den Typ von x herleiten kann, muss allerdings für jede ViewModel Klasse eine eigene Version von OnPropertyChanged implementiert werden. Das finde ich aber noch lästiger als den String direkt hinzuschreiben.

Eine Lösung besteht darin, zusätzlich this als ersten Parameter zu übergeben:

OnPropertyChanged(this, x => x.Name);

Der Compiler kann so den Typ von x über die Signatur der Funktionsdefintion erschließen. OnPropertyChanged kann dadurch in eine Basisklasse verschoben werden und ist wie folgt definiert:

protected void OnPropertyChanged<T, TResult>(T dummy, Expression<Func<T, TResult>> propertyExpression)
{
  OnPropertyChanged(((MemberExpression)propertyExpression.Body).Member.Name);
}

Der Parameter this wird vom Compiler dazu verwendet herzuleiten, dass x vom Typ ViewModel sein muss. So kann OnPropertyChanged vollständig generisch sein. Der Wert von this wird aber gar nicht verwendet.

Das Ganze funktioniert zwar, ist aber insofern etwas seltsam, als dass this nur zur Herleitung des Typs von x verwendet wird. Es ist also noch keine optimale Lösung.

Da wir ja nur den Namen der Property wissen wollen, können wir die Lambda Expression etwas einfacher hinschreiben:

OnPropertyChanged(() => Name);

Das passende OnPropertyChanged sieht dann so aus:

protected void OnPropertyChanged<TResult>(Expression<Func<TResult>> propertyExpression)
{
  OnPropertyChanged(((MemberExpression)propertyExpression.Body).Member.Name);
}

Diese Variante verwende ich selbst in Silverlight und WPF Projekten, da wir stets mit einer ViewModel Basisklasse arbeiten.

Hier der gesamte Quellcode:

public abstract class ViewModelBase : INotifyPropertyChanged
{
  public event PropertyChangedEventHandler PropertyChanged;

  protected void OnPropertyChanged(string propertyName)
  {
    if (PropertyChanged != null)
      PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
  }

  protected void OnPropertyChanged<TResult>(Expression<Func<TResult>> propertyExpression)
  {
    OnPropertyChanged(((MemberExpression)propertyExpression.Body).Member.Name);
  }
}

public class ViewModel : ViewModelBase
{
  public string Name
  {
    get { return _name; }
    set
    {
      if (_name != value)
      {
        _name = value;
        OnPropertyChanged(() => Name);
      }
    }
  }
  string _name;
}

Extension Method

Wer keine gemeinsame Basisklasse für alle seine ViewModel Klassen verwenden möchte, kann auch eine Funktion Raise als Extension Method für den Typ PropertyChangedEventHandler implementieren. Das sieht dann so aus:

public static class PropertyChangedExtensions
{
  public static void Raise<T, TResult>(this PropertyChangedEventHandler handler, T sender, Expression<Func<T, TResult>> propertyExpression)
  {
    if (handler != null)
      handler(sender, new PropertyChangedEventArgs((((MemberExpression)propertyExpression.Body).Member.Name)));
  }
}

Die Extension Method Raise wird direkt am Event Handler aufgerufen:

PropertyChanged.Raise(this, x => x.Name);
PropertyChanged.Raise(this, x => Name);  // same as above

Auch diese Schreibweise ist möglich:

PropertyChanged.Raise(this, () => Name);

Dazu muss in der Definition von Raise nur der erste Parameter des Delegates Func gelöscht werden.

Performance

Die Auswertung des Expression Trees dauert vermutlich mindestens 100 mal länger als der direkte Funktionsaufruf mit einem Stringliteral. Da es sich aber um UI Code handelt spielt das überhaupt keine Rolle. Andere UI Funktionalitäten, wie das Zuweisen von Styles oder Data Templates, dauert um so vieles länger, dass die Verwendung von Expression Trees insgesamt kaum messbar sein wird.

Beim Recherchieren zu diesem Thema habe ich jedoch einen Blogeintrag gefunden, bei dem der Autor in einer ähnlichen Lösung den übergebenen Expression Tree zunächst zerlegt, dann compiliert und schließlich ausführt, um an das sender Objekt zu gelangen. Das ist nicht nur unnötig, sondern vermutlich dann doch auch messbar inperformant.

Aspektorientierte Lösung

Eine aspektorientierte Lösung beispielsweise mit PostSharp halte ich bei diesem Problem für übertrieben. Der zusätzliche Code bei der hier gezeigten Lösung ist minimal und außerdem gut lesbar. Nur um eine Zeile Code im jedem Property-Setter zu eliminieren und im Gegenzug durch ein zusätzliches Attribut zu ersetzten erscheint mir nicht sinnvoll. Es kann aber sein, dass ich meine Meinung dazu zukünftig ändere.

Fazit

Expression Trees haben auch außerhalb von LINQ viele nützliche Anwendungsgebiete. Allerdings sind sie auch etwas komplizierter, was leicht zu einer suboptimalen Verwendung führen kann. Erst beim Schreiben dieses Blogeintrags ist mir beispielsweise eine Verbesserung aufgefallen, die ich vorher übersehen hatte. Ich hatte ursprünglich OnPropertyChanged nicht als generische Funktion geschrieben, sondern stattdessen Func<object> verwendet. Das geht zwar auch, man muss allerdings aufpassen: Wenn der Typ der Property ein Value Type ist, wird im Expression Tree so etwas ähnliches wie „Boxing“ verwendet. Man muss daher eine zusätzliche Variante von OnPropertyChanged mit Func<ValueType> implementieren, die dies durch ein entsprechendes „Unboxing“ berücksichtigt.

Tags: , ,

Silverlight | WPF

Powered by BlogEngine.NET 1.6.1.0 - Impressum