code it

Martins Tech Blog

GZip für WebAPI 2 aktivieren

Mit GZip-Komprimierung kann man einiges an Netzwerklast minimieren - insbesondere, wenn größere Datenmengen übertragen werden sollen, in denen häufig ähnliche Worte vorkommen. Auch wenn das meist genutzte JSON-Format nicht ganz so geschwätzig ist wie beispielsweise SOAP, so kommen doch auch hier beispielsweise die Eigenschaftsnamen in jedem Objekt wieder vor. Wenn man dann eine Liste von Objekten überträgt, so ergibt sich hier ein Einsparungspotenzial.

Im  Gegensatz zu Content-Negotiation, die die Web-API selbst übernimmt, gibt es offenbar keinen Automatismus für die Gzip-Komprimierung. Eine Suche ergab, verschiedene Lösungsansätze - z.B. den von Ben Foster oder den von Radenko Zec. Letzterer ist Vorlage für meine jetzige Lösung geworden.

Schritt 1 - Erzeugen eines ActionFilters, der die Response ändert
public class CompressionAttribute : ActionFilterAttribute
{
    public override async Task OnActionExecutedAsync(HttpActionExecutedContext context, CancellationToken cancellationToken)
    {
        var acceptEncoding = context.Request.Headers.AcceptEncoding;
        var acceptsGzip = acceptEncoding.Contains(new System.Net.Http.Headers.StringWithQualityHeaderValue("gzip"));

        if (!acceptsGzip)
        {
            return;
        }

        var content = context.Response.Content;
        if (content == null)
        {
            return;
        }

        var headers = context.Response.Content.Headers;
        var bytes = await content.ReadAsByteArrayAsync();

        var zlibbedContent = (await Compress(bytes)) ?? new byte[0];
        context.Response.Content = new ByteArrayContent(zlibbedContent);

        foreach (var header in headers)
        {
            if (header.Key.Equals("Content-Length", StringComparison.OrdinalIgnoreCase))
            {
                continue;
            }
            context.Response.Content.Headers.Add(header.Key, header.Value);
        }
        context.Response.Content.Headers.Add("Content-Encoding", "gzip");
    }

    private static async Task Compress(byte[] value)
    {
        if (value == null)
        {
            return null;
        }

        using (var output = new MemoryStream())
        {
            using (var gzipStream = new GZipStream(output, CompressionMode.Compress, CompressionLevel.BestSpeed))
            {
                gzipStream.FlushMode = FlushType.Finish;
                await gzipStream.WriteAsync(value, 0, value.Length);
                   
            }
            return output.ToArray();
        }
    }
}
Um die Komprimierung selbst kümmert sich Ionic.Zlib

Die Implementierung ist an sich recht einfach. Zunächst wird geprüft, ob der Client Gzip-Encoding akzeptiert. Ist das nicht der Fall, dann verändert das Attribut die Ausgabe nicht. Anderenfalls wird die Ausgabe komprimiert und die Response neu aufgebaut. Durch die Neuzuweisung der Response werden auch alle bisherigen Content-Type-Header verworfen, weswegen diese im Nachgang wieder gesetzt werden müssen.

Im Anschluss wird dann dem Client noch mitgeteilt, dass es sich um komprimierten Inhalt handelt.

Schritt 2 - ActionFilter anwenden
Der ActionFilter kann nun verwendet werden, indem eine Web-API-Methode damit annotiert wird oder man aktiviert ihn global für alle Anfragen.
public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        ...

        config.Filters.Add(new CompressionAttribute());

        ...
    }
}
Das passiert in der WebApiConfig.

Attribute in Metadaten prüfen

Heute wurde mir eine gute Frage zu einem Problem gestellt, das ich bisher als trivial abgetan habe: Wie ermittle ich in ASP.NET für eine Eigenschaft des Models, ob dort ein bestimmtes Attribut gesetzt ist? Solche Attribute (insbesondere die vom Typ ValidationAttribute) werden ja verwendet, um Eingabevalidierung vorzunehmen.

Einstiegspunkt soll eine ExtensionMethod auf HtmlHelper sein, wie man sie in ASP.NET häufig findet und die einfach nur einen Text in der Html-Seite ausgeben soll.
public static MvcHtmlString RequiredMark<TModel, TValue>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TValue>> expression)
{
    var required = expression.IsRequired();
    return new MvcHtmlString((!required ? "kein " : string.Empty) + "Pflichtfeld");
}
Spannender Teil daran ist jetzt die schon verwendete ExtensionMethod IsRequired, die für die eigentliche Magie zuständig ist. Diese ist auch noch recht trivial: Sie prüft die Eingabe und ruft die noch zu erstellende Methode HasAttribute auf, die dann die eigentliche Prüfung auf das gesuchte Attribute durchführt:
private static bool IsRequired<T, V>(this Expression<Func<T, V>> expression)
{
    var memberExpression = expression.Body as MemberExpression;
    if (memberExpression == null)
        throw new InvalidOperationException("Expression must be a member expression");

    return HasAttribute(memberExpression.Member, typeof(RequiredAttribute));
}
Auch diese Methode ist recht einfach implementiert - schließlich gibt es in der Klasse Attribute eine statische Methode IsDefined, die dafür verwendet werden kann:
private static bool HasAttribute([NotNull] MemberInfo memberInfo, [NotNull] Type attributeType)
{
    if (memberInfo == null)
    {
        throw new ArgumentNullException();
    }

    var isDefinedOnMember = Attribute.IsDefined(memberInfo, attributeType);

    return isDefinedOnMember;
}
Funktioniert super ..... Funktioniert super und deswegen hören hier die meisten Lösungsvorschläge in einschlägigen Foren auch schon auf.... Funktioniert super, so lange man das Attribut direkt auf dem Model definiert. Allerdings gibt es auch die Möglichkeit, Metadatentypen zu definieren, die dann die Attribute beinhalten. Das ist immer dann sinnvoll, wenn das eigentliche Modell automatisch generiert wird - aus einem Designer oder aus einem T4-Template.

Schauen wir uns ein Beispiel an:
public class MyViewModel
{
    [Required]
    public string Name { get; set; }
}
Dafür funktioniert die bisher erstellte Lösung. Aber bei dem folgenden Beispiel schlägt unsere Prüfung fehl:
[MetadataType(typeof(MyViewModelMetaData))]
public class MyViewModel
{
    public string Name { get; set; }
}

public class MyViewModelMetaData
{
    [Required]
    public string Name { get; set; }
}

Wie kann das Problem gelöst werden? Ganz einfach - zusätzlich zu der schon erstellten Prüfung auf direkt gesetzte Attribute muss nun noch dem Metadatenattribut auf dem Typ gefolgt werden und in diesem Typ geprüft werden welche Attribute auf dem gleichnamigen Member gesetzt sind:
private static bool HasAttribute([NotNull] MemberInfo memberInfo, [NotNull] Type attributeType)
{
    if (memberInfo == null)
    {
        throw new ArgumentNullException();
    }

    // hier prüfe ich direkt gesetzte Attribute
    var isDefinedOnMember = Attribute.IsDefined(memberInfo, attributeType);
    if (isDefinedOnMember)
    {
        return true;
    }

    // jetzt wird noch der Metadatentyp geprüft
    var type = GetMetadataType(memberInfo);
    if (type == null)
    {
        return false;
    }

           
    return type.GetProperties().Any(prop => prop.Name.Equals(memberInfo.Name, StringComparison.OrdinalIgnoreCase) && Attribute.IsDefined(prop, attributeType));
}
Nun liefert die erstellte Methode auch in diesem Fall korrekte Ergebnisse.

Happy coding.

Wo ist eigentlich Temp?

Immer wieder gern verwendet ist der temporäre Ordner, um da Dateien zwischenzuspeichern die später noch gebraucht werden - sei es, um dort Dateioperationen auszuführen, die In-Memory nicht möglich sind oder um Dateien abzulegen, die dann von einem anderen Programm geöffnet werden sollen.

Aber wo ist eigentlich Temp und hab ich da immer Schreibrechte? Die kurze Antwort lautet: a) es hängt von der Umgebung ab und b) ja, dort sind Schreibrechte vorhanden.

Lokale Anwendungen
Lokale Anwendungen, die unter einem Benutzeraccount ausgeführt werden verwenden den Pfad, der in den Pfadangaben des Benutzers als TEMP angegeben ist. Also in der Regel "/AppData/Local/Temp" im Benutzerprofil.

Webseiten im IIS Express
Der IIS Express (also der Entwicklungsserver für z.B. ASP.NET Webseiten) läuft ebenfalls unter dem Benutzeraccount des aktuellen Benutzers und damit landen temporäre Dateien genau wie bei anderen lokalen Anwendungen im TEMP-Ordner des Benutzerprofils.

Webseiten im IIS
Im IIS sieht das Ganze schon etwas anders aus. Hier gibt es auch einen Temp-Ordner. Allerdings wird hier der System-Temp-Ordner verwendet - dieser sollte sich im Standard unter C:\Windows\Temp befinden.

Webseiten im Azure
Auch im Azure gibt es einen temporären Ordner auf dem man Schreibrechte hat. Dieser befindet sich unter "C:\DWASFiles\Sites\<sitename>\Temp".

Noch ein kleiner Hinweis: Der temporäre Ordner ist sehr komfortabel, aber bitte liebe Entwickler räumt dort auch wieder auf und löscht Dateien die ihr erstellt habt und nicht mehr braucht auch wieder.

Raspberry Pi Tweety

Der Raspberry Pi rockt. Damit kann man ziemlich coole Sachen machen. Allerdings ist es besonders für Entwickler, die bisher eher auf der .NET-Plattform unterwegs sind eine kleine Überwindung - ist es doch nur eine Platine und es läuft kein Windows drauf.

Mit ein bisschen Einarbeitung alles kein Problem. Ich möchte kurz zeigen, dass man mit den richtigen Werkzeugen ziemlich nah an der gewohnten Umgebung und damit dann alles ganz einfach ist. Mein Raspberry soll seine aktuelle CPU-Temperatur per Tweet versenden - ok, zugegeben etwas nerdig ist das schon, aber was solls.

Inbetriebnahme
Im Grunde braucht es erst einmal nur einen Raspberry, eine SD-Karte und ein Netzwerkkabel. Das Installationspaket findet man auf der Raspberry-Website. Ich hab mich für Raspbian entschieden. Für die Installation des Betriebssystem-Images hat sich der Win32DiskImager bewährt. Im Anschluss daran einfach die SD-Karte in den Raspberry reinstecken und das Strom- und das Netzwerkkabel anschließen - schon bootet er. 

Die restliche Konfiguration findet auf dem Gerät selber statt. Dazu findet man zunächst die IP-Adresse heraus, die der Router dem neuen Familienmitglied gegeben hat und verbindet sich dann mittels Putty. Mit den Credentials pi/raspberry kann man sich dann verbinden.

Als allererstes sollte man sich hier der Konfiguration und Installation benötigter Komponenten widmen. Folgende Commands empfehlen sich daher:
sudo raspi-config
sudo apt-get update
sudo apt-get upgrade
Mit Hilfe des erstem Commands vergrößert man die Partition auf die Größe der SD-Karte und gibt seinem Nutzer ein neues Kennwort. Mit den anderen beiden Commands aktualisiert man die Pakete auf das aktuelle Patchlevel.

Vorbereitung für die Ausführung von .NET-Programmen
Debian ist Linux, und mit Hilfe von Mono können wir hier .NET Programme laufen lassen. Das hat den großen Vorteil, dass die Entwicklung ganz gut im Visual Studio stattfinden kann und die Assembly dann mit Hilfe der Mono-Runtime auf dem Pi ausgeführt wird. Die meisten Stichpunkte sind schon gefallen... wir brauchen ein paar Pakete:
 
sudo apt-get install mono-runtime
sudo apt-get install libmono-system-core4.0-cil
Damit wird die Mono-Runtime und System.Core und damit die LINQ-Unterstützung installiert.

Teil 1: Erstellen einer .NET Konsolen-Anwendung
Ja genau, das ist alles! Wir erstellen eine .NET Konsolenanwendung im Visual Studio. 
using System;

namespace UniqueSoftware.Raspberry.StatusTweeter
{
    class Program
    {
        static void Main(string[] args)
        {
            new Worker().Run();
        }
    }


    public class Worker
    {
        public void Run()
        {
            Console.WriteLine("Hello World");
            
        }

    }
}
Diese Anwendung können wir jetzt Kompilieren und auf unseren Pi kopieren. Wichtig dabei sind die exe-Datei und die config-Datei. Als Tool zum Kopieren verwende ich WinSCP.

Unser Programm kann jetzt schon im Putty ausgeführt werden. Dafür einfach
mono ./UniqueSoftware.StatusTweeter.exe
eingeben. Ganz klar steht nach Mono der Pfad zur hochgeladenen Assembly - also den ggf. anpassen.

Teil 2: Auslesen der Temperatur
Die CPU-Temperatur kann mit Hilfe des Commands
vcgencmd measure_temp
oder auch
cat sys/class/thermal/thermalzone*/temp
ausgelesen werden. Die zweite Lösung hat den Charme, dass hier einfach eine unformatierte Zahl geliefert wird, womit das Wegschneiden der Maßeinheit wegfällt, allerdings sind es Tausendstel-Grad-Celsius.

Was macht dieses Command? Es öffnet alle Dateien die den Namen temp haben und in den Ordnern thermal/thermalzone* liegen, wobei * ein Platzhalter ist. Um das Auflösen des Platzhalters kümmert sich die Shell. Aus diesem Grund können wir das so nicht verwenden - aber ist auch kein Problem - wir können ja mit Hilfe des .NET Frameworks alle Unterordner in thermal ermitteln und dann das gleiche tun.
public IList<float> DetermineCpuTemperatures()
{
    var folder = new DirectoryInfo("/sys/class/thermal/");
    var subFolders = folder.GetDirectories("thermal_zone*");
    var fileResults = subFolders.Select(subFolder => Path.Combine(subFolder.FullName, "temp"))
        .Select(File.ReadAllText)
        .ToList();

    var result = new List<float>();
    foreach (var fileResult in fileResults)
    {
        int temperature;
        if (!int.TryParse(fileResult, out temperature))
        {
            continue;
        }

        var temperatureInDegree = ((float)temperature) / 1000;
        result.Add(temperatureInDegree);
    }

    return result;
}

Teil 3: Tweet versenden
Zunächst wird eine Twitter-App benötigt, weil ein API-Key und ein Secret benötigt wird. Dazu meldet man ich im Twitter Developer Portal mit seinem Twitter-Account an und legt eine neue App an. Wichtig ist, dass man der App Lese- und Schreibberechtigungen gibt. Auf der API-Key Seite dort sind 4 Keys relevant: API-Key und -Secret sowie der Access-Token mit dem zugehörigen Secret.

Mit Hilfe von NuGet wird nun TweetSharp hinzugefügt. Es fehlen nun in unserem Programm noch 2 Funktionen - eine die den Text des Tweets erzeugt und eine die den Tweet absetzt.
public string BuildTweet(IList<float> temperatures)
{
    if (temperatures.Count == 1)
    {
        return string.Format("Hier ist der Pi und meine CPU-Temperatur ist {0:f2} °C.",
            temperatures[0]);
    }

    return string.Format("Hier ist der Pi und meine CPU-Temperaturen sind {0}.",
        temperatures.Select(x => string.Format("{0:f2} °C", x)));
}

public void SendTweet(string message)
{
    const string API_KEY = "ri8VPJluqJTFiOKqNg5lTZ0AA";
    const string API_SECRET = "EQrg5XeatwSzmMlnB296ukSPJUlWNwLXsaPDhVPjaRuoBG5px1";
    const string ACCESS_TOKEN = "2312268955-Y2wOLzCy2Iw5ctK20N4SqdSHK9Zs6VvlInZUmEm";
    const string ACCESS_TOKEN_SECRET = "2LTmy2JRkGmFqLxggthASmvwpi7SKxwiJoq772BaHY7TL";

    var twitterApp = new TwitterService(API_KEY, API_SECRET);
    twitterApp.AuthenticateWith(ACCESS_TOKEN, ACCESS_TOKEN_SECRET);

    var tweet = new SendTweetOptions();
    tweet.Status = message;

    var result = twitterApp.SendTweet(tweet);
    if (twitterApp.Response.InnerException != null)
    {
        throw twitterApp.Response.InnerException;
    }
}
Die Hauptmethode des Programms ändert sich damit dann wie folgt:
public void Run()
{
    try
    {
        var temperatures = DetermineCpuTemperatures();
        var tweetText = BuildTweet(temperatures);
        SendTweet(tweetText);
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex);
    }
}
Das war's auch schon fast. Im Windows läuft das Programm schon. Im Mono läuft es im Standard erst einmal nicht, weil noch keine vertrauenswürdigen Zertifikate vorhanden sind. Das ändert man, indem man auf dem Pi  die Zertifikate von Mozilla importiert.
sudo mozroots --import --ask-remove
Sollte das nicht ausreichend sein, so kann man das komplette Mono Development Paket installieren und im Anschluss dem Zertifikatsmanager die Twitter-Seite hinzufügen.
sudo apt-get install mono-devel
sudo certmgr -ssl https://api.twitter.com
Ab sofort werden bei jedem Programmstart die CPU-Temperaturen an eure Follower geschickt. 

Sei nett zu deinem DBA...

... und setz den Application Name im ConnectionString.

Wer schon einmal in die Verlegenheit kam, und prüfen musste, warum es auf einer Datenbank zu Fehlern kommt und welche Anwendung diese erzeugt, der wird froh sein, wenn der Application Name gesetzt war.

Worum geht es?

Beim Verbindungsaufbau zum SQL Server wird nicht nur Benutzername und Kennwort, sondern auch ein Anwendungsname ausgetauscht. Dieser Anwendungsname wird in Tracing-Tools (wie z.B. dem Profiler) auch angezeigt.

Nun ist es für die Fehlersuche sehr sinnvoll, wenn dieser Anwendungsname eindeutig ist.

Leider wird in der Praxis häufig vergessen, diesen zu setzen, was dazu führt, dass hier nur der Standardwert .NET SQLClient Data Provider steht. Das ist für die Problemsuche nicht sehr hilfreich. Mit den neueren Projekt-Templates von Visual Studio, in denen Entity Framework verwendet wird, hat sich dieses Problem nur etwas verlagert. Hier ist der Parameter zwar schon im ConnectionString enthalten, wird aber auf den neuen Standardwert EntityFramework gesetzt - auch nicht sehr hilfreich.

Wie setzt man den ApplicationName?

Im ConnectionString gibt es den Parameter Application Name oder die gleichbedeutende Abkürzung App. Dieser muss gesetzt werden. 

Beispiele:

<connectionStrings>
    <add name="MyEntities" connectionString="metadata=res://*/Models.MyModel.csdl|res://*/Models.MyModel.ssdl|res://*/Models.MyModel.msl;provider=System.Data.SqlClient;provider connection string="data source=.;initial catalog=MyDb;integrated security=True;MultipleActiveResultSets=True;App=Meine Anwendung"" providerName="System.Data.EntityClient" />
</connectionStrings>
<connectionStrings>
    <add name="DefaultConnection" connectionString="Server=.;Database=MyDb;Integrated Security=SSPI;Application Name=Meine Anwendung;" providerName="System.Data.SqlClient" />
</connectionStrings>

Das geht sowohl in klassischen ConnectionStrings als auch in Connectionstrings für das Entity Framework.

SetWindowHookEx - cool oder nur Fingerübung?

Die Anforderung ist recht schnell erklärt: Erkenne, wann ein Fenster einer Anwendung maximiert, minimiert oder wiederhergestellt wird. "Das kann doch so schwer nicht sein" denkt man sich da, schließlich wirft Windows ja nur so mit Nachrichten um sich, die man einfach nur abfangen muss. Die Windows-API ist dein Freund. Ok, ganz so einfach ist es dann doch nicht, sonst bräuchte dieser Blogpost ja nicht geschrieben werden.

Damit das Fensterhandling und auch so ziemlich alles andere in Windows funktioniert, werden Nachrichten an Fenster gesendet. Zumeist macht Windows das selbst (z.B. Fenster 358: Maximiere dich"). Kommt diese Nachricht am Fenster an, gibt es dort eine Kette von "Abonnenten" - die hook chain. Und in diese Kette kann man eigene Funktionen eingliedern lassen, die dann Teil dieser Kette werden.

Mehr...

Field oder Property, das ist hier die Frage

Manchmal ergeben sich im persönlichen Gespräch Grundsatzdiskussionen über Programmierstile und Herangehensweisen und so ist es auch in diesem Beispiel, welches sich erst kürzlich zugetragen hat. Die Frage ist: Auto-Property oder öffentliches Feld - welcher Variante ist der Vorrang zu geben?

Möchte man Daten aus einer Klasse nach außen freigeben, so ist die einfachste Möglichkeit, einfach ein Feld zu deklarieren und diesem den Zugriffsmodifizierer public zu verpassen:

public class Person
{
    public string LastName;
    public string FirstName;
}

Diese Möglichkeit gibt es, Sie widerspricht aber dem Konzept der Kapselung. Datenzugriffe mit Eigenschaften zu kapseln bietet zum einen die Möglichkeit einen dedizierten Zugriff auf private Informationen des Objektes zu gewährleisten und vielleicht auch noch eine Validierung der Daten vorzunehmen, die dann gespeichert werden sollen. Im Standardfall sieht das Ganze so aus:

private string _name;
public string Name
{
    get { return _name; }
    set { _name = value; }
}

Damit gibt es ein privates Feld, das über die Eigenschaft nach außen verfügbar gemacht wird und im Setter können dann Prüfungen durchgeführt werden.

Seit C# 3.0 gibt es nun die Möglichkeit, autoimplemented Properties zu verwenden:

public string Name { get; set; }

Damit ist noch immer eine Eigenschaft vorhanden, jedoch ist die Anlage des sogenannten Backing Fields damit nicht mehr notwendig - das übernimmt der Compiler. Was damit auch entfällt, ist die Möglichkeit zusätzliche Prüfungen im Setter hinzuzufügen.

Dieser Logik folgend, stellt sich nun aber die Frage: Warum sollte ich autoimplemented Properties verwenden und nicht public Fields (wie im ersten Beispiel). Die eben genannten offensichtlichen Vorteile der Eigenschaften entfallen bei autoimplemented Properties ja.

Grund 1: Asymmetrische Sichtbarkeit 

Eigenschaften erlauben die Definition asymmetrischer Sichtbarkeit von Getter und Setter:

public string Name { get; private set; }
public string DateOfBirth { private get; set; }

Ein Feld ist immer komplett public oder komplett private, nicht aber private beschreibbar und öffentlich schreibgeschützt.

Grund 2: Definition in Interfaces

Eigenschaften können in Interfaces festgeschrieben werden, während das für Felder nicht möglich ist.

public interface IPerson
{
    string LastName { get; set; }
    string FirstName { get; set; }
}

public class Student : IPerson
{
    public string LastName { get; set; }
    public string FirstName { get; set; }
}

Bei der Verwendung von Feldern kann man nicht sicherstellen, dass alle Objekte vom Typ Student über öffentlich verfügbare Felder für Nachname und Vorname verfügen.

Grund 3: Überschreibbarkeit

Felder können nicht überschrieben werden. Während man bei Eigenschaften problemlos in Basisklassen eine Deklaration mit virtual oder abstract vornehmen kann, um sicherzustellen, dass ein bestimmter Wert immer vorhanden ist, und die Initialisierung dann ggf. aber doch an eine spezialisiertere Klasse zu delegieren, so ist das mit Feldern nicht möglich.

Grund 4: Abwärtskompatibilität

"Dieser Code wird sich nie ändern" gibt es nicht und auch an der noch so undenkbaren Stelle wird irgendwann einmal der Zeitpunkt gekommen sein, an dem irgendeine Änderung notwendig ist. Und dann ist es gut gewappnet zu sein und eben nicht auf das Feld angewiesen zu sein, sondern die Auto-Property vielleicht aufzubrechen und hier noch zusätzliche Dinge zu machen. Der große Vorteil dann: Die Schnittstelle nach außen muss sich vielleicht nicht zwingend ändern und bereits bestehender Code der die Komponente verwendet, kann in gleicher Art und Weise weiter laufen ohne breaking changes.

Grund 5: Data Binding

Möchte man früher oder später die Objekte zur Datenbindung in WPF verwenden, so benötigt man Properties. Klar ist in diesem Fall keine Änderungsbenachrichtigung möglich - es sei denn man verwendet Aspekte, aber verwendbar sind sie so schon.

Wahrscheinlich gibt es noch den ein oder anderen weiteren Grund. Ich denke, die obige Liste bietet noch Platz für Erweiterungen.

Als Sprecher bei der .NET Usergroup Leipzig

Am 25.06. war ich zu Gast bei der .NET Usergroup Leipzig. Thema das Abends war modulares Anwendungsdesign und wie Prism dabei unterstützen kann. Nach ein paar Slides, in denen die theoretischen Grundlagen etwas beleuchtet wurden, ging es auch schon ans Live-Coding.

Die Gruppe war mit unter 10 Teilnehmern recht überschaubar, was aber den großen Vorteil hatte, dass wir bei einigen aufgetretenen Fragen direkt einsteigen konnten. Und auch ich habe dank Marcel dort noch etwas gelernt zu einem Feature von Resharper, das mir bisher unbekannt war.

Ich fand es einen sehr gelungenen Abend und auch das netzwerken im kleinen Kreis nach der Usergroup im Kesselhaus war ein kleiner Event.

Für alle diejenigen, die das Live-Beispiel noch einmal nachvollziehen wollen, sind hier die Slides - 20120625_UGLeipzig_Prism.zip (1,78 mb) - und das Code-Sample - Prism_Slides.pdf (753,54 kb).

PS: Bitte denkt daran, dass das Beispiel Directory-Discovery verwendet.

WIX und das "Unhandled Extension Element"

Zur Erstellung eines Installers für eine Webanwendung verwende ich WIX (Windows Installer XML). Teil dieses Installers ist es auch, das Virtual Directory im IIS anzulegen. Dafür gibt es die WIX IIS Extensions. Um diese verwenden zu können, muss man einfach einen neuen XML-Namespace hinzufügen - so die Information in verschiedenen Blogbeiträgen zum Thema. Gesagt - getan:

<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"
     xmlns:iis="http://schemas.microsoft.com/wix/IIsExtension">
</Wix>

Und schon funktioniert Intellisense im Visual Studio und man kann die Erweiterungen iis:WebVirtualDir oder iis:WebApplication verwenden. Was diese Blogeinträge nicht verraten: Versucht man nun einen Build des Projekts, so wirft Visual Studio die Fehlermeldung

The Component element contains an unhandled extension element 'iis:WebVirtualDir'.  Please ensure that the extension for elements in the 'http://schemas.microsoft.com/wix/IIsExtension' namespace has been provided.

Problem ist nun nämlich noch, dass zwar der Namespace deklariert ist, aber die Implementierung der Erweiterung nicht gefunden werden kann, weil die dazu passende Assembly nicht referenziert ist. 

Die Lösung des Problems ist dann auch denkbar einfach: Zusätzlich zur Deklaration des Namespaces IIsExtension muss auch die Assembly WixIIsExtension.dll referenziert werden.

RSS-Feeds an eigene Bedürfnisse anpassen

In meinem letzten Post hab ich eine kleine Einführung in das Thema RSS-Feeds mit .NET erzeugen gegeben. Nun sind aber nicht alle Inhalte, die man per RSS-Feed übertragen möchte in den Standard-Elementen unterzubringen, die die RSS-Spezifikation definiert. Typische Beispiele dafür sind zusätzliche Geodaten oder Daten zu Medieninhalten in Podcasts. Um auch dieser Anforderung gerecht zu werden, bieten die beiden vorgestellten Klassen SyndicationFeed und SyndicationItem noch die Möglichkeit eigene Attribute oder eigene Elemente zu definieren. Wie das geht, soll an einem kleinen Beispiel gezeigt werden:

Eigene Elemente zum Item hinzufügen

Möchte man einfach nur zusätzliche Elemente zu den Items hinzufügen, kommt man recht schnell ans Ziel. Schlüssel sind die ElementExtensions. Im Beispiel werden jedem Item noch die Elemente Latitude und Longitude zur Definition einer Geokoordinate hinzugefügt.

var item = new SyndicationItem
{
    Id = "http://myfeeds/2011/06/" + i,
    Title = SyndicationContent.CreatePlaintextContent("Wichtige Mitteilung"),
};

item.ElementExtensions.Add("Latitude", null, 34.2543);
item.ElementExtensions.Add("Longitude", null, 14.2545);

Das Resultat ist, dass wie gewünscht das Item-Element über die definierten Sub-Elemente verfügt.

<item>
    <guid isPermaLink="false">http://myfeeds/2011/06/0</guid>
    <title>Wichtige Mitteilung</title>
    <Latitude>34.2543</Latitude>
    <Longitude>14.2545</Longitude>
</item>

Den gleichen Weg kann man dann auch beim Einlesen der Daten wieder verwenden: In der Auflistung ElementExtensions befinden sich die hinzugefügten Elemente mit den jeweiligen Namen und Inhalten. Mit Hilfe der generischen Methode GetObject kann man dann auf die Inhalte zugreifen.

var feed2 = SyndicationFeed.Load(reader);
foreach (var item in feed2.Items)
{
    var latitudeElementExtension = item
        .ElementExtensions
        .Single(e => e.OuterName == "Latitude");
    var latitude = latitudeElementExtension.GetObject<double>();
}

Analog zu den ElementExtensions gibt es die AttributeExtensions, die (wie sollte es anders sein) keine Elemente sondern Attribute erzeugen.

Eigene komplexe Objekte zum Item hinzufügen

ElementExtensions erlauben nicht nur das Hinzufügen primitiver Datentypen sondern auch komplexer Objekte - Bedingung: Sie müssen xml-serialisierbar sein und es muss ein passender XML-Serializer existieren oder es muss sich um einen DataContract handeln. In meinem Fall definiere ich mir nun eine neue Klasse GeoCoordinate, die sich als Location serialisiert.

[XmlRoot("Location", Namespace = "http://ns.uniquesoftware.de/location")]
[Serializable]
public class GeoCoordinate
{
public GeoCoordinate()
{
}

 public GeoCoordinate(double lat, double lon)
 {
     Latitude = lat;
     Longitude = lon;
 }

 [XmlElement("Latitude")]
 public double Latitude { get; set; }
 [XmlElement("Longitude")]
 public double Longitude { get; set; }
}

Im Anschluss definiere ich wie vorhin auch eine ElementExtension und füge dieser der Auflistung des Elements hinzu.

var item = new SyndicationItem
{
    Id = "http://myfeeds/2011/06/" + i,
    Title = SyndicationContent.CreatePlaintextContent("Wichtige Mitteilung"),
};
item.ElementExtensions.Add(
    new GeoCoordinate(34.2543, 14.2545), 
    new XmlSerializer(typeof(GeoCoordinate)));

Das resultierende XML im Feed beinhaltet nun wie erwartet die eben definierten Tags, die über die definierten Namespaces verfügen.

<item>   
    <guid isPermaLink="false">http://myfeeds/2011/06/0</guid>
    <title>Wichtige Mitteilung</title>
    <Location 
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:xsd="http://www.w3.org/2001/XMLSchema"
        xmlns="http://ns.uniquesoftware.de/location">
        <Latitude>34.2543</Latitude>
        <Longitude>14.2545</Longitude>
    </Location>
</item>

Eigene Namespaces verwenden

Häufig trifft man auf Konstrukte, in denen der Namespace nicht explizit jedes Mal wieder als Attribut im Element definiert wird, sondern wo nur noch eine Namespace-Referenz existiert. Nichts einfacher als das: Dazu fügt man einfach die XML-Namespace-Deklaration der AttributeExtensions-Auflistung des SyndicationFeed-Objektes hinzu. Im folgenden Beispiel habe ich dafür den Namespace "my" deklariert.

// create new syndication feed 
var feed = new SyndicationFeed(
    "Mein Blog Feed",
    "Wissenwertes über ASP.NET, WPF und Silverlight",
    new Uri("http://feeds.feedburner.com/discoveringmicrosofttechnologies"))
    {
        Language = "de-DE",
        Copyright = new TextSyndicationContent("Unique Software"),
        Generator = "Unique Software News Generator"
    };
feed.AttributeExtensions.Add(
    new XmlQualifiedName("my", "http://www.w3.org/2000/xmlns/"), 
    "http://ns.uniquesoftware.de/location");

Der Effekt ist, dass nun der Namespace im XML-Element channel definiert und dann automatisch eine Namespace-Referenz aufgebaut wird.

<item>
    <guid isPermaLink="false">http://myfeeds/2011/06/0</guid>
    <title>Wichtige Mitteilung</title>
    <my:Location>
        <my:Latitude>34.2543</my:Latitude>
        <my:Longitude>14.2545</my:Longitude>
    </my:Location>
</item>

Zusammenfassung

ElementExtensions und AttributeExtensions sind eine gute Möglichkeit, RSS-Feeds flexibel auf die eigenen Bedürfnisse anzupassen und mit Hilfe des Objektmodells typsicher auf die Objekte beinhalteten Objekte zuzugreifen. Besonders bei XML-Strukturen, die nur noch sehr entfernt etwas mit dem ursprünglichen Feed-Objektmodell zu tun haben, wird es wohl immer wieder eine Abwägung von Aufwand und Nutzen sein, ob man ElementExtensions und die Syndication-Objekte verwendet oder ob man die XML-Struktur "schnell eben selbst" per Linq-To-Xml zusammenbaut. Ich persönlich finde die sich bietenden Möglichkeiten wieder recht umfangreich und finde es schade, dass die Klassen so ein unbeachtetes Dasein fristen. 

Englische Version Englische Version