code it

Martins Tech Blog

Ein Wochenende beim Developer Open Space

An diesem Wochenende war ich beim Developer Open Space in Leipzig, der nun schon einige Male dort stattgefunden hat. Leider konnte ich auch dieses Jahr aus Zeitgründen nicht an den wirklich interessanten Workshops am Freitag teilnehmen, aber Samstag und Sonntag war ich vor Ort. 

Das Konzept ist recht einfach: Versammle eine Menge Entwickler und gib ihnen den Freiraum, selbst Themen zu bestimmen über die sie reden wollen. Was initial mal als .NET Open Space gestartet ist, hat sich in den letzten Jahren von der Technologie entfernt. Und so kommt es, dass inzwischen auch Entwickler mit dabei sind, die in anderen Technologien zu Hause sind, wie Java, Ruby, JavaScript und so weiter.

Welche Themen waren für mich interessant? Nun ja, dadurch, dass ich mich auch beruflich mit IoT beschäftige, fand ich die Wearables und Smarthome-Sessions sehr spannend. Als Freelancer/Geschäftsführer auch sehr interessant war die anschließende Session bei der es darum ging, gescheiterte Projekte zu analysieren und die Meinungen anderer zu hören, wie sie und auch ihre Auftraggeber damit umgehen. Leider war die Event Storming Session parallel, so dass ich bei dieser nicht dabei sein konnte. Weil auch wir oftmals vor Technologieentscheidungen stehen, besuchte ich anschließend die Session zum Thema NoSql vs. RDBMS in der diskutiert wurde, wann man welche Technologie sinnvollerweise einsetzt. Die für mich letzte Session des Tages hielt ich mit einigen Usergroup-Leads gemeinsam, in der wir zeigten, wie wir die Seite der .NET Usergroup Dresden aufgebaut haben (Jekyll auf Github). Es folgten noch zwei weitere Slots, aber die Zeit habe ich eher für entspannte Gespräche auf dem Hof genutzt. Der Abend endete dann in entspannter Atmosphäre und durchaus auch lustigen Gesprächen im Pub.

Der Sonntag lief recht ähnlich ab: interessante Sessions, spannende Gespräche in den Pausen oder nebenher. 


Es war für mich wieder ein Erlebnis, mit den ganzen Leuten quer aus dem DACH-Raum fachsimpeln und Gedanken austauschen zu können. Und auch wenn ich eher Technologie oder Management-Sessions besucht habe, so gab es auch dieses Jahr wieder Sessions zu Softskill und Psychologie - es ist also für jeden etwas dabei. Und mit 200 Anmeldungen zählt dieser Event nun schon zu den größeren Konferenzen.

Facebook zeigt falschen Vorschau-Content an

In der letzten Woche war mir aufgefallen, dass bei einer meiner Seiten der falsche Inhalt in der Vorschau angezeigt wurde. Das Problem hatte mehrere Ursachen.

1. IPv6
Das erste Problem ließ sich recht einfach lösen: Ich hatte in der Domain-Konfiguration noch einen AAAA-Record auf die IPv6  des Hosters gesetzt. Offenbar versucht Facebook einen Zugriff über IPv6 durchzuführen. Damit kam der Inhalt der Vorgabeseite des Hosters und damit auch ein hübsches rotes Verkehrszeichen als Bild, was mit meiner Webseite nun so überhaupt nichts zu tun hat.

2. Cache
Nun wurde es aber spannend. Die IP-Zuordnung war korrekt und trotzdem wurde noch der alte Inhalt angezeigt. Problem 2 ist, dass Facebook die Inhalte cacht. Das ist natürlich nur dann ein Problem, wenn man die Inhalte ändert. Glücklicherweise gibt es von Facebook ein Tool (das eigentlich einen anderen Zweck hat), aber hier sehr hilfreich ist - der Open Graph Object Debugger. Hier kann man die Url eingeben und sich ansehen, was Facebook daraus ausliest zum Erzeugen der Vorschau. Und man kann hier auch erzwingen, dass die Daten neu abgerufen werden.

3. Bilder
Letztes Problem bei mir waren Bilder. Auf vielen Webseiten ist es gerade üblich, im Header einen Slider mit Bildern zu haben. Diese Bilder findet auch der Crawler und möchte in der Vorschau eine Galerie anzeigen. Das ist aber nicht immer gewünscht. Nun kann man mit Meta-Informationen ein bestimmtes Bild erzwingen. Der entsprechende Metatag ist:

<meta property="og:image" content="http://komplette.url/zum/bild.jpg" />

Wichtig zu beachten: Das Bild muss mindestens 200 x 200 Pixel groß sein. Und: Größe und Seitenverhältnis definieren, ob das Vorschaubild links neben den anderen Metainformationen angezeigt wird (quadratisches Bild) oder ob sich das Bild über die komplette Breite des Posts zieht.

noHistory in der Activity und der Seiteneffekt

Normalerweise kann man im savedInstanceState Statusinformationen ablegen, die dann beim Resume der Activity dafür sorgen, dass die Benutzeroberfläche wieder so hergestellt werden kann, wie sie war bevor die App in den Ruhemodus versetzt wurde.

Heute hatte ich das Phänomen, dass im onSaveInstanceState meine Daten in das Bundle geschrieben wurden, beim Fortsetzen der App im onCreate aber immer NULL als savedInstanceState bereitstand, was dafür sorgte, dass sich die Benutzeroberfläche immer zurücksetzte auf den Initialzustand.

Nach einiger Suche kam ich dann auf des Problems Lösung: Ich hatte in der Manifest-Datei der Activity das Flag noHistory="true" gesetzt. Laut Dokumentation hat dies folgenden Effekt:
Whether or not the activity should be removed from the activity stack and finished [...] when the user navigates away from it and it's no longer visible on screen.

[...] 

A value of "true" means that the activity will not leave a historical trace. It will not remain in the activity stack for the task, so the user will not be able to return to it.
Mein Verständnis dieses Flags war, dass dadurch die Activity nicht auf dem Backstack landet. Das funktioniert auch. Allerdings meint "the user will not be able to return to it" auch, dass sämtliche Informationen im Bundle verworfen werden und jedes Resume wie ein Neustart der Activity ist.

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.

Constraints für WebAPI-Parameter

Seit die WebAPI in der Version 2 vorliegt, kann man dort nicht nur basierend auf Konventionen agieren, sondern die Routen auch mit Hilfe der Attribute RoutePrefix und Route definieren. Schauen wir uns ein Beispiel an:

[RoutePrefix("api/v1/books")]
public class BooksController : ApiController {

    [HttpGet]
    [Route("{id}")]
    public IHttpActionResult GetItem(int id)
    {
        // in production mode we would search for the book in database
        if (id == 87653)
        {
            var book = new Book
            {
                Author = "John Grisham",
                Title = "Gray Mountain",
                Id = 87653,
                Isbn = "978-0385537148"
            };
            return Ok(book);
        }

        return NotFound();
    }
}

Aufrufbar ist das Ganze nun über die Route /api/books/87653.


In der Action habe ich definiert, dass die Id vom Datentyp int sein soll. Was passiert, wenn hier kein int übergeben wird, also wenn hier z.B. die ISBN übergeben wird?


ASP.NET kann den übergebenen Wert nicht mappen und unser Parameter ist nicht Nullable. Deswegen wird der Request mit dem Statuscode 400 abgewiesen.

WebAPI unterstützt auch sogenannte Constraints. Damit kann näher spezifiziert werden, welche Bedingungen die Parameter erfüllen müssen. In meinem Fall ist der Parameter id ein int. Das definiere ich nun zusätzlich in der Route.
[HttpGet]
[Route("{id:int}")]
public IHttpActionResult GetItem(int id)
{
    // do all the fancy stuff
}
Was passiert nun, wenn ein Request mit der ISBN ankommt?


Der Request wird noch immer abgewiesen, aber dieses mal nicht mit Statuscode 400 sondern mit dem Statuscode 404, denn nun wird bereits bei der Auswertung der Routentabelle erkannt, dass keine Route vorliegt, die für diesen Request passt.

Solche Constraints können auch kombiniert werden. Dazu verbindet man verschiedene Constraints einfach mit einem Doppelpunkt. 

[HttpGet]
[Route("{id:int:min(1):max(100000)}")]
public IHttpActionResult GetItem(int id)
{
    // do all the fancy stuff
}

Eine detaillierte Liste der möglichen Constraints können in der MSDN nachgelesen werden.

Wem die möglichen Constraints nicht ausreichen, dem steht es frei, eigene Constraints zu schreiben. Ich möchte das am Beispiel eines ISBN13-Constraints machen. Das Vorgehen ist ganz einfach. 

Zunächst erstellt man eine neue Klasse die das Interface IHttpRouteConstraint implementiert:
public class Isbn13Constraint : IHttpRouteConstraint
{
    private static bool IsIsbn13Valid(string inputValue) { 
        // remove delimiters 
        var isbn = Regex.Replace(inputValue, @"-|\.| ", ""); 

        // validate number of digits 
        if (isbn.Length != 13)
        {
            return false;
        }
        // calculate the product of the digit multiplication operations 
        var product = 0;
        for (var i = 0; i < (isbn.Length - 1); i++)
        {
            product += Convert.ToInt16(isbn.Substring(i, 1)) * (1 + (2 * (i % 2)));
        } 

        // calculate the check digit 
        var  checkdigit = Convert.ToString(10 - (product % 10)); 

        // validate check digit 
        return (checkdigit == isbn.Substring(isbn.Length - 1, 1)); 
    } 

    public bool Match(HttpRequestMessage request, IHttpRoute route, string parameterName,
        IDictionary values, HttpRouteDirection routeDirection)
    {
        object value;
        if (!values.TryGetValue(parameterName, out value) || value == null)
        {
            return false;
        }

        var stringValue = value as String;
        return stringValue != null && IsIsbn13Valid(stringValue);
    }
}

Der Rückgabewert der einzige Methode Match ist selbsterklärend - true wenn alles passt, false wenn nicht. Der hier eben erzeigte Constraint übernimmt gleich noch die Validierung der Gültigkeit der ISBN. Zugegeben - man kann sich darüber streiten, ob das in das Aufgabengebiet eines Constraints fällt. Schon allein wegen des Rückgabecodes "Not Found" statt "Bad Request" würde ich diese Prüfung eher woanders ansiedeln.

Dieser neue Constraint muss nun in der WebApiConfig noch registriert werden. Statt MapHttpAttributeRoutes ohne Parameter aufzurufen, übergeben wir hier einen neuen ConstraintResolver, der den eben erstellten Constraint enthält.

var constraintResolver = new DefaultInlineConstraintResolver();
constraintResolver.ConstraintMap.Add("isbn13", typeof(Isbn13Constraint));

config.MapHttpAttributeRoutes(constraintResolver);
Das war's auch schon fast. Der Constraint kann nun ebenso wie auch die anderen Constraints verwendet werden.

[HttpGet]
[Route("{isbn:isbn13}")]
public IHttpActionResult GetItem(string isbn)
{
    // do all the fancy stuff
}
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.

Mit dem Service Broker auf Datenänderungen reagieren

Im SQL Server gibt es seit längerem den SQL Server Service Broker. Mit dessen Hilfe kann man live auf Datenänderungen reagieren. Beispiel gefällig?

Ich habe eine sehr einfache Bibliotheksdatenbank. Enthalten ist nur eine Tabelle (dbo.Books) mit den Spalten ID, Author und Title. Und ich habe eine WPF-Anwendung in der in der MainView per Entitiy Framework die Bücher geladen und an die Liste gebunden werden.
<Window x:Class="MyApp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <ListBox ItemsSource="{Binding BookList}" >
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding Title}"/>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
    </Grid>
</Window>

public class MainViewModel
{
    public MainViewModel()
    {
        ReloadData();
    }

    private void ReloadData()
    {
        using (var entitites = new LibraryEntities())
        {
            bookList.Clear();
            foreach (var book in entitites.Books)
            {
                bookList.Add(book);   
            }
        }
    }

    private readonly ObservableCollection bookList = new ObservableCollection();

    public ObservableCollection BookList
    {
        get
        {
            return bookList;
        }
    }
}

So weit erst einmal kein Hexenwerk. Der spannende Teil kommt jetzt.

Seit ADO.NET 2.0 gibt es die Klasse SqlDependency, mit der Änderungen an den Daten überwacht werden können. Diese kann für diesen Zweck auch hier verwendet werden.

SqlDependency verfügt über 2 statische Methoden Start und Stop und wie der Name schon vermuten lässt, kann man damit das Tracking starten und auch wieder beenden. In meinem Beispiel sind diese im Konstrukor und im Dispose des ViewModels. Der restliche Code wird in der Reload-Methode angefügt...
public class MainViewModel : IDisposable
{
    private string connectionString;

    public MainViewModel()
    {
        connectionString = new LibraryEntities().Database.Connection.ConnectionString;
        dispatcher = Dispatcher.CurrentDispatcher;
        SqlDependency.Start(connectionString);
        ReloadData();
    }

    private void ReloadData()
    {
        using (var entitites = new LibraryEntities())
        {
            bookList.Clear();
            foreach (var book in entitites.Books)
            {
                bookList.Add(book);   
            }
        }

        using (var connection = new SqlConnection(connectionString))
        {
            connection.Open();
            using (var command = new SqlCommand("SELECT Title, Author FROM dbo.Books", connection))
            {
                command.Notification = null;
                var dependency = new SqlDependency(command);
                dependency.OnChange += OnDependencyChange;
                command.ExecuteReader(CommandBehavior.CloseConnection);
            }
        }
    }

    private void OnDependencyChange(object s, SqlNotificationEventArgs e)
    {
        ((SqlDependency)s).OnChange -= OnDependencyChange;

        if (e.Type == SqlNotificationType.Change)
        {
            dispatcher.Invoke(this.ReloadData);
                
        }
    }

    private readonly ObservableCollection<Books> bookList = new ObservableCollection<Books>();

    private Dispatcher dispatcher;

    public ObservableCollection<Books> BookList
    {
        get
        {
            return bookList;
        }
    }

    public void Dispose()
    {
        SqlDependency.Stop(connectionString);
    }
}

... und hier beginnt es etwas schmutzig zu werden.

SqlDependency basiert auf den Möglichkeiten von ADO.NET 2.0. Damit wird hier eine SqlConnection benötigt und ein SqlCommand, das mit einem DataReader auch ausgeführt werden muss. Und es gibt noch ein paar weitere Punkte zu beachten:
  1. Auf der Datenbank muss der Broker aktiviert sein.
  2. Der Benutzer benötigt ausreichende Berechtigungen (SUBSCRIBE QUERY NOTIFICATIONS)
  3. Der Command bei der Initialisierung der SqlDependency muss bestimmten Voraussetzungen entsprechen (also z.B. nicht SELECT * FROM ....)

Alle diese Einschränkungen kann man ausführlich nochmal auf CodeProject nachlesen.

Während der Ausführung ist es dann wichtig zu wissen, dass der Event ein One-Shot ist - heißt er wird nur bei der ersten Änderung ausgelöst. Deshalb muss dann wenn die Daten neu geladen werden auch der Event wieder registriert werden.

Wenn man das alles berücksichtigt, dann kann man damit aber recht coole Sachen machen. Und man ist nicht auf die WPF beschränkt. Mit der Hilfe von OWIN und SignalR auch weitreichender über Datenänderungen informieren.

Bedingte Kompilierung im Android Studio

Wer wie ich aus der .NET-Welt kommt, der kennt das Feature der bedingten Kompilierung. Ein klassisches Beispiel dafür ist ein Codeschnipsel wie dieser hier:
var config = new Config();
#if DEBUG
    config.EndPoint = "http://localhost/testendpoint/";
#else
    config.EndPoint = "http://myapp.endpointsoftheworld/";
#endif
    new Wizard().DoSomeMagic(config);
Was passiert hier? Je nach aktueller Buildkonfiguration können Variablen definiert werden. Sind diese gesetzt kann mit Precompiler Switches auf diese Variablen zugegriffen werden und ein bestimmter Teil des Codes wird in die Kompilierung einbezogen oder auch nicht. Die wohl bekannteste dieser Precompiler Variablen ist DEBUG, welche wie der Name schon vermuten lässt im Debug-Build gesetzt ist.

Damit kann man erreichen, dass beispielsweise in Testbuilds anderer Code ausgeführt oder andere Einstellungen verwendet werden oder dass man bestimmte Module in seiner Anwendung zur Verfügung hat oder auch nicht. Als Szenario wäre hier denkbar dass man eine Free- und eine Bezahlvariante seiner Anwendung anbietet.

Genau diese Anwendungsfälle sind auch im Bereich der App-Entwicklung durchaus gängige Szenarien. Allerdings unterstützt Java und damit auch Android solche Preprozessor Direktiven nicht. 

Eine durchaus gängige Lösung ist es, mit anwendungsweiten Konstanten zu arbeiten:
public final class Debug {
  //set to false to allow compiler to identify and eliminate
  //unreachable code
  public static final boolean ON = true;
} 
var config = new Config();
if (Debug.ON) {
    config.EndPoint = "http://localhost/testendpoint/";
} else {
    config.EndPoint = "http://myapp.endpointsoftheworld/";
}
new Wizard().DoSomeMagic(config);
Der Kompiler erkennt die Konstante und im eigentlichen Code ist dann auch nur der Code enthalten, der gewünscht ist. 

Diese Lösung funktioniert zwar und ist auch recht ähnlich zu den Precompiler Switches, hat aber einen entscheidenden Nachteil: Die Konstante muss jedes mal von Hand auf den korrekten Wert gesetzt werden. Ein Automatismus wie bei .NET ist nicht vorgesehen und wie schnell können so Fehler passieren und plötzlich laufen Testcalls gegen die echte API und zerstören Daten.

In Android Studio wird Gradle als Buildssystem verwendet. Damit sind automatisierte Builds möglich. Und genau hier kann man ansetzen, um das Problem zu lösen. Denn Gradle unterstützt Sourcesets und Flavors. "Debug" und "Release" sind bereits mit dem Projektsetup enthalten. Mit diesem Wissen kann man nun einfach neben dem eigentlichen Main-Ordner auf gleicher Ebene zwei neue Ordner an und benennt diese "debug" und "release". Hier kann man nun Packages und Quellcodedateien ablegen, die je nach Buildvariante enthalten sein sollen.


In obiger Konfiguration wird nun die Klasse Configuration aus debug verwendet, wenn ich assembleDebug aufrufe und nach gleicher Logik die gleichnamige Klasse Configuration aus release beim Aufruf von assembleRelease.

Meine Empfehlung ist in Java wie auch in .NET, mit solchen Switches umsichtig umzugehen. Die Komplexität der Anwendung steigt ungemein je mehr solcher Switches verwendet werden und verringern die Wartbarkeit.

HTTPS-Redirect - aber konfigurierbar bitte

Manchmal möchte man erzwingen, dass komplette Seiten oder Subdomains - oder um es anders zu formulieren: ganze ASP.NET Webanwendungen - nur per HTTPS erreichbar sind. Real-World-Beispiele gibt es zuhauf: Banking-Seiten, Shops usw.

Eine Möglichkeit ist es, das Ganze fix im Code zu verdrahten. In ASP.NET gibt es ein Filter-Attribut, das genau diesen Redirect vornimmt - das RequireHttpsAttribute. Dieses kann man einfach über die Controller oder Actions schreiben oder eben für den hier betrachteten Fall als globalen Filter definieren.

public class FilterConfig
{
    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        filters.Add(new HandleErrorAttribute());
        // für die ganze Seite HTTPS erzwingen
        filters.Add(new RequireHttpsAttribute());
    }
}

Nun möchte man aber vielleicht nicht immer auf die HTTPS-Seite weiterleiten, sondern nur in bestimmten Fällen. Ich würde beispielsweise gern auf meiner Entwicklungsmaschine auf SSL und den Aufwand den man dafür betreiben muss verzichten. Und ich hätte es gern konfigurierbar. Eine andere Instanz der Anwendung braucht dieses strikte Verhalten vielleicht nicht.

Dafür gibt es auch was - nämlich die web.config. Unter dem Key system.webServer können Redirects definiert werden. Ein entsprechender Eintrag könnte beispielsweise wie folgt aussehen:
<system.webServer>
  <rewrite>
    <rules>
      <rule name="HTTP to HTTPS redirect" stopProcessing="true">
        <match url="^(.*)$"/>
        <conditions>
          <add input="{HTTPS}" pattern="^OFF$" ignoreCase="true" />
          <add input="{HTTP_HOST}" matchType="Pattern" pattern="^localhost(:\d+)?$" negate="true" />
          <add input="{HTTP_HOST}" matchType="Pattern" pattern="^127\.0\.0\.1(:\d+)?$" negate="true" />
        </conditions>
        <action type="Redirect" redirectType="Permanent" url="https://{HTTP_HOST}/{R:1}" />
      </rule>
    </rules>
  </rewrite>
</system.webServer>

Was wird hier gemacht? Wie der Name schon andeutet, wird ein permantener (also Statuscode 301) Redirect vorgenommen - allerdings mit ein paar Ausnahmen: Wenn der Host localhost oder 127.0.0.1 ggf. noch gefolgt von einem Port ist, wird auf den Redirect verzichtet, denn dann befinde ich mich entweder auf dem Webserver oder auf der Entwicklungsmaschine und greife darüber zu. Eigentlich ganz einfach - oder?

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.