.
Anmeldung | Registrieren | Hilfe

.NET-Blogs Archiv August 2014

Surface Pro 3 Systempartition verkleinern

29.08.2014 15:55:00 | Stefan Lange

Gestern habe ich pünktlich zum angegebenen Liefertermin meinen Surface Pro 3 erhalten. Nach dem ersten Start hatte das System wie allgemein üblich nur eine Partition mit einer Größe von ca. 500GB. Da dies eher unpraktisch praktisch ist, wollte ich als erstes die Systempartition verkleinern. Leider konnte ich über die Datenträgerverwaltung die Größe der Partition zunächst lediglich auf ca. 320GB verringern.

Die richtige Vorgehensweise finde ich nicht so ganz logisch, daher hier die Kurzanleitung.

So verkleinert man das Systempartition

  1. BitLocker für Laufwerk C: aktivieren
    Auf dem Surface Pro 3 ist das System-Volume bereits im Auslieferungszustand verschlüsselt, wird aber beim Start von Windows automatisch freigegeben, wenn BitLocker noch nicht aktiviert wurde. Zum Deaktivieren von BitLocker muss man ihn aber erst mal aktivieren. Also über das Kontextmenü von Laufwerk C: „BitLocker aktivieren“ auswählen.
  2. Passwort festlegen
    Im nachfolgenden Wizard Passwort eingeben und ggf. den Wiederherstellungsschlüssel sichern. Die Aktivierung selbst benötigt keine weitere Rechenzeit, da das Volume ja bereits verschlüsselt ist.
  3. BitLocker deaktivieren
    Nach dem Aktivieren gibt es im Kontextmenü nun den Eintrag „BitLocker verwalten“. Diesen Punkt aufrufen und dann „BitLocker deaktiveren“ auswählen. Die Systempartition wird anschließend entschlüsselt, was ein wenig dauert.
  4. Volume verkleinern
    Nun kann man in der Datenträgerverwaltung „Volume verkleinern…“ aufrufen und die gewünschte Größe einstellen.
  5. Anschließend BitLocker wieder aktivieren.

Hintergrund

Vermutlich legt Bitlocker versteckte Dateien auf dem verschlüsselten Volume an. Da man versteckte Dateien nicht verschieben sollte, lässt sich die Partition mit Bordmitteln nur bis zu der Stelle verkleinern, wo diese Dateien liegen. Das Deaktivieren von BitLocker entschlüsselt das Volume, die Datei(en) verschwinden, das Verkleinern funktioniert.

async await und parallel - eine einfache Messung

23.08.2014 10:02:21 | Paul Mizel

Es ist nicht immer sinnvoll async und await zu verwenden, auch in einem Parallel-Szenario, für wenige Operationen hat es sogar Nachteile. Nachmessen hilft. class Program{ static void Main(string[] args) { int runs = 10000; using (Monitor.Create("Task: ")) { for (int i = 0; i < runs; i++) { Task task = new Task(ProcessDataAsync); task.Start(); task.Wait(); } } using (Monitor.Create("Normal: ")) { for (int i = 0; i < runs; i++) { ProcessData(); } } using (Monitor.Create("Parallel Task: ")) { Parallel.For(0, runs, i => { Task task = new Task(ProcessDataAsync); task.Start(); task.Wait(); }); } using (Monitor.Create("Parallel Normal: ")) { Parallel.For(0, runs, i => { ProcessData(); }); } Console.ReadKey();    } static async void ProcessDataAsync() { Task<int> task = Task.FromResult(5); System.Threading.Thread.Sleep(1); int x = await task; } static void ProcessData() { System.Threading.Thread.Sleep(1); } } public class Monitor : IDisposable { System.Diagnostics.Stopwatch sw; string prefix; public Monitor(string prefix) { this.prefix = prefix; sw = System.Diagnostics.Stopwatch.StartNew(); } public static Monitor Create(string prefix) { return new Monitor(prefix); } public void Dispose() { Console.WriteLine(prefix + sw.ElapsedMilliseconds + " ms"); } }

noHistory in der Activity und der Seiteneffekt

22.08.2014 12:08:00 | Martin Hey

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.

Playing around with SignalR 2.1.1 - Teil 2: Die Clientseite

21.08.2014 15:25:00 | Jürgen Gutsch

Im ersten Teil habe ich die Serverseite beschrieben. Diesmal geht es um die wesentlich interessantere Clientseite.

Vorbereitung – Das Formular

Als erste habe ich zwei einfache Formulare definiert. Das erste ist ein einfaches An- und Abmeldeformular, das lediglich einen Benutzername erwartet. Neben einer Textbox, beinhaltet das Formular je einen Button zum Anmelden und eines zum Abmelden vom Chat. Wie man sieht nutze ich die vorhanden CSS-Klassen aus Twitter Bootstrap und muss mich nicht groß um das Layout kümmern:

<form class="form-horizontal">
    <fieldset id="loginwindow">
        <legend>Sign-In</legend>

        <div class="form-group">
            <label for="name"
                   class="col-sm-2 control-label">
                User name:
            </label>
            <div class="col-sm-10">
                <input type="text"
                       class="form-control"
                       name="name"
                       id="name"
                       placeholder="User name">
            </div>
        </div>
        <div class="form-group">
            <div class="col-sm-offset-2 col-sm-10">
                <button type="button"
                        id="signout"
                        class="btn btn-default pull-right"
                        disabled="disabled">Sign-out</button>
                <button type="button"
                        id="signin"
                        class="btn btn-primary pull-right">
                    Sign-in
                </button>
            </div>
        </div>
    </fieldset>
</form>

Das zweite Formular ist das eigentliche Chatfenster. Den Chat gebe ich der Einfachheit halber in einer Textarea aus. Darunter befinden sich eine Textbox zur Eingabe der Chat-Nachrichten und daneben ein Button zum Versenden der Nachricht. Die beiden Formulare sitzen direkt untereinander Optisch durch das Fieldset getrennt:

<form class="form-horizontal">
    <fieldset id="chatwindow"
              disabled="disabled">
        <legend>Chat</legend>

        <div class="form-group">
            <div class="col-sm-offset-2 col-sm-10">
                </textarea class="form-control"
                          style="height: 300px;"
                          name="chatarea"
                          id="chatarea">
            </div>
        </div>

        <div class="form-group">
            <label for="name"
                   class="col-sm-2 control-label">
                Your message:
            </label>
            <div class="col-sm-10">
                <input type="text"
                       class="form-control"
                       name="message"
                       id="message"
                       placeholder="Your message">
            </div>
        </div>
        <div class="form-group">
            <div class="col-sm-offset-2 col-sm-10">
                <button type="button"
                        id="messagesend"
                        class="btn btn-primary pull-right">
                    Send
                </button>
            </div>
        </div>
    </fieldset>
</form>

Auf zum Coden – Das JavaScript

Das war es auch schon auf der HTML-Seite. Schauen wir uns nun mal das JavaScript an. Das erste was getan werden sollte ist, den benötigten Hub zu holen:

var chatter = $.connection.chatter;

Die “connection” wird über die Bibliothel jQuery.signalR bereitgestellt und stellt den Zugriff auf den jeweiligen Hub bereit. Wir erinnern uns an das Attribut über unserem Hub aus dem ersten Teil. Über diesen Namen greifen wir nun darauf zu. Weiter unten sehen wir mehr davon.

Vorerst benötigen wir drei Methoden die unsere Formulare steuern.

Die erste Methode “addMessage” erstellt die komplette Chat-Nachricht, inklusive des Clientseitigen Zeitstempels (Was bringt dem User die Serverzeit? Die Nachrichten werden in diesem Beispiel eh nicht auf dem Server gespeichert, sondern nur durchgeleitet ;) ) Ist die Nachricht komplett, wird sie an den Inhalt der Textarea angehängt:

var addMessage = function (message) {
    var currentdate = new Date();
    var messate = currentdate.getHours() + ":" + currentdate.getMinutes()
    messate += ' ' + message + '\n';
    $('#chatarea').append(messate);
};

var enableChat = function () {
    $('#chatwindow').prop("disabled", false);
    $('#name').prop("disabled", true);
    $('#signin').prop("disabled", true);
    $('#signout').prop("disabled", false);
};

var disableChat = function () {
    $('#chatwindow').prop("disabled", true);
    $('#name').prop("disabled", false);
    $('#signin').prop("disabled", false);
    $('#signout').prop("disabled", true);
    $('#name').val('');
};

Die beiden Methoden “enableChat” und “disableChat” werden aufgerufen, wenn die Anmeldung oder die Abmeldung erfolgreich war und steuern welcher Teil der UI aktiv oder inaktiv ist.

Als nächsten Schritt habe ich, wie auch im Sample, eine “init” Methode geschrieben, mit der die Buttons der Formulare mit Aktionen versehen werden. Hier passiert auch der Aufruf der Serverseitigen Methoden:

var init = function () {

    $('#signin').click(function () {
        var name = $('#name').val();
        if (name.length < 4) {
            alert('username is not valid!');
            return;
        }

        chatter.server
            .signIn($.connection.hub.id, name);
    });

    $('#signout').click(function () {
        var name = $('#name').val();

        chatter.server
            .signOut($.connection.hub.id, name);
    });

    $('#messagesend').click(function () {
        var message = $('#message').val();
        if (message.length < 1) {
            alert('message is not valid!');
            return;
        }

        chatter.server
            .post($.connection.hub.id, message);
        $('#message').val('');
        $('#message').focus();
    });
};

Die Validierung die wir hier sehen, ist nicht wirklich gut, reicht aber für dieses Beispiel voll aus ;)

Über “chatter.server” haben wir nun Zugriff auf die serverseitigen Methoden. In jedem Fall übergeben wir hier die ConnectionId, die sich unter $.connection.hub.id versteckt. Wie auch für die Anmeldung, wird auch bei der Abmeldung der Name übergeben, damit wir eine schöne Begrüßung oder einen Abschiedsnachricht erzeugen können.

Schaut euch im ersten Teil die zugehörigen Server-Methoden an um den Datenfluss nachvollziehen zu können.

“chatter.server.post” schickt nun die eigentliche Chat-Nachricht an den Server, der sie daraufhin an alle Clients sendet.

Wie aber empfängt der Client nun die Nachrichten? Wir Erinnern uns an den serverseitigen Aufruf aus dem ersten Teil:

Clients.All.postMessage(user + "> " + message);

Wir müssen auf der Clientseite – unter anderem – die Methode “postMessage” erstellen und dem clientseitigen “chatter”-Objekt bekannt geben:

$.extend(chatter.client, {
    userAdded: function () {
        enableChat();
    },
    postMessage: function (message) {
        addMessage(message);
    },
    userRemoved: function () {
        disableChat();
    }
});

Wir erzeugen einen “client”-Namespace und fügen die benötigten Methoden ein, die dann wiederum auf die vorhandenen Methoden zugreifen.

That’s it :)

Ich kann nun den Chat laufen lassen und mit zwei verschiedenen Browser testen:


[Update]

NHibernate 4.0 ist fertig

21.08.2014 03:32:00 | Jürgen Gutsch

Es tut sich wieder etwas bei NHibernate :)

Wie Ayende Rahien eben veröffentlicht hat, ist NHibernate 4.0 released und steht auf SourceForge oder per NuGet zum Download bereit.

Die aktuellen Release Notes, sowie die Sourcen findet ihr auf GitHib: https://github.com/nhibernate/nhibernate-core

Somit haben wir noch etwas dass ich mir in der nächsten Zeit genauer anschauen werde :)

Playing around with SignalR 2.1.1 - Teil 1: Die Serverseite

20.08.2014 14:25:00 | Jürgen Gutsch

Ich weiß, das Thema SignalR ist schon etwas älter. Umso wichtiger ist es aus meiner Sicht SignalR endlich mal anzuschauen. Seit meiner Selbständigkeit und auch danach, sind einige sehr interessante Themen liegen geblieben, die ich mir aus Zeitgründen nicht anschauen konnte. Eines dieser Themen ist eben auch SignalR, welches (stand heute) in der Version 2.1.1 unter der Apache 2.0 License für Mono und .NET zur Verfügung steht.

Die offizielle Website ist http://signalr.net/, die Dokumentation auf GitHub zu finden. Viele weitere sehr detaillierte Tutorials, Videos und Samples sind allerdings auf http://www.asp.net/signalr zu finden.

Um was geht’s?

SignalR ist im Prinzip ein Client- UND Serverseitiger Wrapper um das WebSocket-Protokoll mit eingebautem Fallback für Clients die WebSockets nicht unterstützen.

Das WebSocket-Protokoll ist ein auf TCP basierendes Netzwerkprotokoll, das entworfen wurde, um eine bidirektionale Verbindung zwischen einer Webanwendung und einem WebSocket-Server bzw. einem Webserver, der auch WebSockets unterstützt, herzustellen.-Protokoll
(http://de.wikipedia.org/wiki/WebSocket)

WebSockets werden heute von nahezu allen modernen Browsern und Web Servern unterstützt

Setup

Ich habe mich entschlossen ein MVC 5 Projekt als Spielwiese zu nutzen und einen kleinen Chat zu realisieren

Das Setup ist kein Hexenwerk. Die nötigen Bibliotheken sind schnell per NuGet eingebunden. Da ich keine Lust hatte erst Dokus zu lesen, habe ich mich entschlossen gleich das Paket mit den SignalR Samples zu installieren:

Install-Package Microsoft.AspNet.SignaR.Sample

Darauf hin habe ich alle nötigen Komponenten im Projekt eingebunden. Eine Readme Datei weist mich darauf hin, dass ich eine Startup Klasse in meinem Projekt hinzufügen muss um SignalR zu initialisieren.

Die Startup-Klasse klingt ganz nach Owin. Was sich dann auch so herausstellt. SignalR wird als Owin-Komponente bereitgestellt. Das ist Klasse. So fängt das an Spaß zu machen :)

Ein letzter Schritt ist nötig, damit SignalR läuft. Wir müssen ein Script-Verweis auf eine Route machen, die von der SignalR Owin Komponente behandelt wird, um die sog. Hubs auf der Clientseite bereitzustellen. Das tue ich in der “scripts”-Sektion direkt auf der Seite auf der mein Chat laufen soll:

@section scripts
{
    <script src="../signalr/hubs"></script>
    @Scripts.Render("~/bundles/chatter")
}

Das Bundle “~/bundles/chatter” bindet die chatter.js ein, mit der ich auf der Clientseite mit SignalR arbeite. Mehr dazu später.

[Update]

In der BundleConfig.cs muss die JavaScript Bibliothek “jquery.signalR” noch eingebaut werden. Das habe ich direkt im Bundle vom jQuery getan:

bundles.Add(new ScriptBundle("~/bundles/jquery").Include(
            "~/Scripts/jquery-{version}.js",
            "~/Scripts/jquery.signalR-{version}.js"));

[/Update]

Das Sample

Das Beispiel das per NuGet installiert wurde zeigt einen Stock Ticker, ist schön klein und übersichtlich gehalten und läuft auf Anhieb. Die beste Grundlage um mir das eine oder andere abzuschauen.

Aufgerufen wird es über “~/SignalR.Sample/StockTicker.html”

Schaut es euch am besten selber an :)

Auf geht’s

Ich entscheide mich als, den Chat in der gleiche Struktur umzusetzen wie im StockTicker Sample.

Ich fange dafür am besten mit dem sog. “Hub” an. Der Hub definiert die Schnittstelle wie sie vom Client aus aufgerufen werden kann, zudem kann er über den IHubConnectionContext direkt mit den Clients kommunizieren.

[HubName("chatter")]
public class ChatHub : Hub
{
    private readonly Chatter _chatter;

    public ChatHub() :
        this(Chatter.Instance)
    {
    }

    public ChatHub(Chatter chatter)
    {
        _chatter = chatter;
    }

    public void SignIn(string connectionId, string name)
    {
        _chatter.SignIn(connectionId, name);
    }

    public void Post(string connectionId, string post)
    {
        _chatter.Post(connectionId, post);
    }

    public void SignOut(string connectionId, string name)
    {
        _chatter.SignOut(connectionId, name);
    }
}

Das Attribut “HubName” definiert den Hub wie er vom Client aus gesehen wird. Wichtiger ist die Basisklasse “Hub” von der hier geerbt wird. Die eigentliche Arbeit ist dann in der Klasse Chatter die als Singleton implementiert ist. Interessant ist, dass ich die Methodennamen nicht extra auszeichnen muss. Auf der Clientseite kommen Sie dennoch sauber mit Camel-Casing an (also signIn, post, signOut)

Die ConnectionId muss mir der Client explizit mitgeben. Das geht leider nicht aus dem Sample hervor, aber eine kleine Google-Recherche half sofort. Anders als bei einem StockTicker, habe ich es beim Chat mir Usern zu tun die ich erkennen muss. So muss der User der sich einloggt, explizit begrüßt werden, während die anderen Teilnehmer einen Hinweis über einen neuen Teilnehmer bekommen.

Die Klasse “Chatter” ist mit Hilfe vom Typ System.Lazy<> als Singleton angelegt. Singletons habe ich auf diese Art noch nicht umgesetzt, funktioniert aber, sieht gut aus und kann man öfter mal so machen:

private readonly static Lazy<Chatter> _instance = new Lazy<Chatter>(
    () => new Chatter(GlobalHost.ConnectionManager.GetHubContext<ChatHub>().Clients));
public static Chatter Instance
{
    get { return _instance.Value; }
}

Der Chatter bekommt einen IHubConnectionContext<dynamic> injiziert, was die Client API abbildet, denn schließlich soll ich ja auch Methoden auf dem Client aufrufen können. Diesen Context speichere ich in einer Eigenschaft mit dem Namen “Clients”:

private IHubConnectionContext<dynamic> Clients { get; set; }

private Chatter(IHubConnectionContext<dynamic> clients)
{
    Clients = clients;
    Users = new Dictionary<string, string>();
}

Wie das aussieht und wie ich die Teilnehmer beim Login anhand der ConnectioID unterschiedlich behandle, ist hier zu sehen:

public void SignIn(string connectionId, string name)
{
    Users.Add(connectionId, name);
    Clients.Client(connectionId).userAdded();
    Clients.Client(connectionId).postMessage("> ## Hello " + name + " ##");
    Clients.AllExcept(connectionId).postMessage("> ## new user '" + name + "' added ##");
}

Hier ist der Grund für die ConnectionId zu sehen: Nachdem der User hinzugefügt wurde, teile ich diesem User explizit mit, dass er hinzugefügt wurde. Anschießend schreibe ich eine Chat-Nachricht direkt an den neuen User raus und am Ende teile ich allen anderen Teilnehmern mit, dass es einen neuen Teilnehmer gibt.

Eine normale Chatnachricht geht wie folgt an alle Clients raus:

public void Post(string connectionId, string message)
{
    string user;
    if (Users.TryGetValue(connectionId, out user))
    {
        Clients.All.postMessage(user + "> " + message);
    }
}

Existiert der User im Pool, geht die Chatnachricht einfach an alle raus.

Bei Ausloggen gehe ich wieder explizit auf die User ein und behandle den entsprechenden User separat:

public void SignOut(string connectionId, string name)
{
    Users.Remove(connectionId);
    Clients.Client(connectionId).userRemoved();
    Clients.Client(connectionId).postMessage("> ## Goodby " + name + " ##");
    Clients.AllExcept(connectionId).postMessage("> ## user '" + name + "' removed ##");
}

Weiter mit Teil 2

Artikel in der aktuellen dotnetpro

20.08.2014 07:26:00 | Jörg Neumann

In der aktuellen dotnetpro (09/2014) sind zwei Artikel von mir erschienen.

Ein Fingerbreit UI - Touch-fähige Oberflächen mit WPF entwickeln
Heute sollte eine Anwendung nicht nur mit Maus und Tastatur zu bedienen, sondern auch Touch-fähig sein. dotnetpro zeigt, worauf dabei zu achten ist, und präsentiert dazu geeignete Steuerelemente.

Eine gemeinsame Basis - Generische Controls im Eigenbau
Generische Controls wie Data- oder PropertyGrids erlauben es, beliebige Datenmengen einfach abzubilden. Auch in Formularen können solche Controls die Arbeit erleichtern. Wie man sie erstellt, lesen Sie hier.

CodeLens Indicators for Git in VS 2013 Ultimate Update 3

20.08.2014 07:20:00 | Jürgen Gutsch

Nachdem ich gestern endlich mal das Update 3 für Visual Studio 2013 installiert habe, ist mir heute endlich auch aufgefallen, dass die CodeLense Indicators erweitert wurden:

Neu sind an zweiter und dritter Stelle der letzte Änderer und der Zeitpunkt der letzten Änderung, sowie die Anzahl der Änderungen. Diese Angaben stammen in meinem Fall aus Git.

Klickt man auf die zweite oder dritte Stelle so sieht man die die Änderungen mit wunderhübschen, denglischen Commit Messages, Datum, User und kann auch direkt die entsprechende Version in den Editor holen

Ein Maus-Over auf der einzelnen Zeile gibt dabei weitere Informationen preis.

Ein bisschen Recherche hat mich zu einem ausführlichen Blogartikel von Jean-Marc Prieur gebracht: Code Lens for Git in Visual Studio 2013 Ultimate Update 3

Jetzt muss die Git Integration im VS nur noch für große Projekte performanter werden, dann würde ich die nicht immer gleich ausschalten ;)

Einladung: Technical Summit 2014 in Berlin im November

19.08.2014 22:48:45 | Kay Giza

Es ist mir eine freude auf das Microsoft Technical Summit 2014, in Berlin im November 2014 aufmerksam zu machen! Denn: Microsoft lädt zum Gipfeltreffen der technischen Experten nach Berlin ein. Top Speaker des Technical Summit 2014 ist sicherlich der neue Microsoft Chief Executive Officer (CEO) Satya Nadella. Microsoft lädt IT-Professionals und Entwickler vom 11. bis 13. November in Berlin zum „Technical Summit 2014“. Die ersten beiden Tage stehen im Zeichen der Konferenz mit ihren insgesamt rund 40 Sessions und Live-Demos, Tag 3 ist... [... mehr auf Giza-Blog.d]

This post is powered by www.Giza-Blog.de | Giza-Blog.de: RSS Feed
© Copyright 2006-2016 Kay Giza. All rights reserved. Legal

Style Cop in Visual Studio einsetzen

19.08.2014 10:13:43 | Hendrik Loesch

Nachdem ich im Juli aufgrund von Urlaub und Projekteinsatz nichts gepostet habe, versuche ich es diesmal gleich mit etwas Neuem. Statt eines Blogposts habe ich ein Video aufgenommen. In diesem zeige ich wie man Style Cop in das Visual Studio einbinden kann, worum es sich bei dem Tool handelt und wie man die hinterlegten Regeln […]

Agile Bodensee Konferenz am 01. und 02 Oktober 2014

19.08.2014 01:27:01 | Jürgen Gutsch

Auch auf der diesjährigen Agile Bodensee Konferenz bin ich wieder dabei. Dieses mal – am 1. Oktober – mit einem Workshop mit dem Titel “TDD Extreme

Was sich dahinter verbirgt? Vor allem provokante Aussagen meinerseits und um das Thema TDD die ich versuche anhand von Beispielen und Übungen zu Beweisen ;)

Interessiert? Dann schreibt doch einfach eine Twitter-PM an @sharpcms oder eine Nachricht per Facebook an juergen.gutsch mit dem Stichwort “TDD@abkon”. Ihr erhaltet dann einen Rabattcode mit dem ihr 10% Rabatt auf ALLE Ticket-Kategorien bekommt und könnt so etwas günstiger an dem Workshop teilnehmen :)

Weitere Informationen zur Agile Bodensee Konferenz 2014 und vor allem die komplette Agenda über die zwei Tage erhaltet ihr direkt auf der Website: www.agile-bodensee.com

Geplanter Umzug meines Blogs

18.08.2014 01:30:03 | Jürgen Gutsch

Im Zuge des Umbaus meiner eigenen Website, habe ich mich dazu entschlossen, auch meinen Blog umzuziehen. Nicht weil das System auf dem Community Server von ASP.NET Zone schlecht wäre, sondern eher deshalb, weil ich mein Blog enger in meine Website integrieren möchte. In den vergangenen Monaten wurde die Website wieder immer mehr eine persönliche Portfolio Seite und das Blog soll der hauptsächliche Content-Lieferant der Website werden.

Auch vorher hatte ich bereits die Idee, dass ich mein Blog mehr in meine Kontrolle holen möchte. Allerdings läuft das bisherige System gut und stabil und es gab keinen zwingenden Grund für einen Umzug. Dennoch hatte ich mir einige vorhandene Systeme angeschaut, die mir aber alle zu umfangreich sind, um sie in meine Website zu integrieren. In vielen Fällen fehlte mir jedoch die MetaWeblog API um das Blog per Windows Live Writer zu bedienen, den ich immer noch sehr gerne verwende. (Tipps und Hinweise zu modernen Blog APIs und modernen Authoring-Tools nehme ich hier sehr gerne entgegen.) Sehr gerne schreibe ich morgens oder Abends offline im Zug um die Posts dann später zu veröffentlichen.

Bis ich nun ein wirklich passendes System finde, tendiere ich dazu ein kleines Blog-Framework zu schreiben, dass ich beliebig in jedes ASP.NET Projekt integrieren kann. Für den Anfang soll der SimpleObjectStore die Persistierung übernehmen. Da für dieses Tool beliebige DataProvider bereitgestellt werden können, kann das Framework dann auch bei Bedarf auf eine SQL Database gehen. Ich für meinen Teil werde den DataProvider für den Azure Table Storage verwenden, da die Website selber auf einer Azure Website gehosted wird.

Ziel ist es eine Bibliothek bereitzustellen, welche die nötigen Controller und Models bereitstellt. Die zugehörigen Views muss dann jeder Nutzer dieser Bibliothek selber bereitstellen:

Controller:

  • BlogController
    • Für Artikel-Listen und Kommentare
  • MetaWeblog Controller
    • Für den Windows Live Writer
    • ggf. per API Controller
  • BlogML Controller
    • Import/Export
  • RSS Controller
    • per API Controller

Models:

  • BlogArticleList
  • BlogArticle
  • Comment
  • Tag

Das wird wohl in den nächsten Monaten meine Beschäftigung neben der Arbeit und Familie sein. Mal sehen wie weit ich damit gehe.

Warum selber machen?

Na, um zu lernen. :) In den letzten Monaten sind interessante Neuerungen zu ASP.NET herausgekommen, die ich natürlich gerne genauer anschauen und umsetzen möchte.

Auch hier gilt: Für Vorschläge zu einer alternativen leichtgewichtigen Lösung bin ich immer zu haben.

Attribute in Metadaten prüfen

15.08.2014 10:08:00 | Martin Hey

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.

CRON job / command to run URL address every 5 minutes

10.08.2014 10:44:00 | Andreas Mehl

The following command displays previously created cron jobs:

crontab -l

 

Create a new cron job and understand the syntax.

The following command opens us the cronjob file with the default editor:

crontab -e

 

If no task was created in the file is only one line with the short form, what each column means.:

# m h dom    mon dow    command

 

 

with wget

*/5 * * * * wget http://blog.mehl-web.de

 

with curl:

*/5 * * * * curl http://blog.mehl-web.de

*/5 * * * * curl --request GET 'http://blog.mehl-web.de/check.php?param1=1&param2=2'

 

to check if it was succesfully:

open /var/log/syslog

find your job:

Aug 10 06:30:01 RechnerName /USR/SBIN/CRON[31301]: (root) CMD (wget http://blog.mehl-web.de)

CRON job / command to run URL address every 5 minutes

10.08.2014 10:44:00 | Andreas Mehl

The following command displays previously created cron jobs:

crontab -l

 

Create a new cron job and understand the syntax.

The following command opens us the cronjob file with the default editor:

crontab -e

 

If no task was created in the file is only one line with the short form, what each column means.:

# m h dom    mon dow    command

 

 

with wget

*/5 * * * * wget http://blog.mehl-web.de

 

with curl:

*/5 * * * * curl http://blog.mehl-web.de

*/5 * * * * curl --request GET 'http://blog.mehl-web.de/check.php?param1=1&param2=2'

 

to check if it was succesfully:

open /var/log/syslog

find your job:

Aug 10 06:30:01 RechnerName /USR/SBIN/CRON[31301]: (root) CMD (wget http://blog.mehl-web.de)

CRON job / command to run URL address every 5 minutes

10.08.2014 10:44:00 | Andreas Mehl

The following command displays previously created cron jobs:

crontab -l

 

Create a new cron job and understand the syntax.

The following command opens us the cronjob file with the default editor:

crontab -e

 

If no task was created in the file is only one line with the short form, what each column means.:

# m h dom    mon dow    command

 

 

with wget

*/5 * * * * wget http://blog.mehl-web.de

 

with curl:

*/5 * * * * curl http://blog.mehl-web.de

*/5 * * * * curl --request GET 'http://blog.mehl-web.de/check.php?param1=1&param2=2'

 

to check if it was succesfully:

open /var/log/syslog

find your job:

Aug 10 06:30:01 RechnerName /USR/SBIN/CRON[31301]: (root) CMD (wget http://blog.mehl-web.de)

CRON job / command to run URL address every 5 minutes

10.08.2014 10:44:00 | Andreas Mehl

The following command displays previously created cron jobs:

crontab -l

 

Create a new cron job and understand the syntax.

The following command opens us the cronjob file with the default editor:

crontab -e

 

If no task was created in the file is only one line with the short form, what each column means.:

# m h dom    mon dow    command

 

 

with wget

*/5 * * * * wget http://blog.mehl-web.de

 

with curl:

*/5 * * * * curl http://blog.mehl-web.de

*/5 * * * * curl --request GET 'http://blog.mehl-web.de/check.php?param1=1&param2=2'

 

to check if it was succesfully:

open /var/log/syslog

find your job:

Aug 10 06:30:01 RechnerName /USR/SBIN/CRON[31301]: (root) CMD (wget http://blog.mehl-web.de)

Constraints für WebAPI-Parameter

05.08.2014 10:08:00 | Martin Hey

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.

Regeln | Impressum