code it

Martins Tech Blog

Access denied due to invalid credentials - aber nur manchmal?

Heute bin ich über ein sehr seltsames Phänomen gestolpert. Gesetzt ist eine ASP.NET MVC3 Anwendung. Im Debug auf dem lokalen IISExpress läuft diese auch wunderbar, und auch auf der Testumgebung (einem Windows Server 2008) läuft die Anwendung - zumindest so lange man mit localhost darauf zugreift. Alle Zugriffe über die IP-Adresse des Servers enden mit einem 401er-Fehler - und das sowohl von anderen Rechnern aus als auch vom Server selbst.

Und das Seltsame daran: Es betrifft auch nur diese eine Anwendung - alle anderen auf diesem Server laufenden Web-Anwendungen laufen problemlos und sind auch Remote erreichbar. Nach mehreren erfolglosen Einstellungsversuchen im IIS7 - Bindings, Berechtigungen und was man sonst alles falsch machen kann, IIS-Neustarts und sogar Serverneustarts ändert sich am Problem trotzdem nichts. Der Zugriff über localhost funktioniert, aber der Zugriff über die IP-Adresse eben nicht.

Letztendlich brachte ein Blogeintrag von John Tindell die rettende Lösung - und auch wenn mir wirklich unklar ist, WARUM das funktioniert - es funktioniert.

Lösung: Fügt man in der web.config im Bereich system.webServer den folgenden Eintrag hinzu und ruft die Seite dann nochmals auf, so funktioniert der Aufruf nun auch mit der IP-Adresse:

<httpErrors errorMode="Details" />

Seltsam daran: Dieser Eintrag hat eigentlich nur etwas damit zu tun, wie das Error-Handling für die Anwendung ist und wie demzufolge Fehlermeldungen ausgegeben werden. Detailed Errors anzuschalten kann natürlich nicht die Lösung sein, da man auf extern erreichbaren Servern natürlich keine Interna des Webservers und der Applikationsstruktur preisgeben möchte. Deswegen kann das noch nicht die finale Lösung sein.

Asynchrone Controller in MVC3

Die Zeiten in denen Anwender bereit waren, lange Wartezeiten in Kauf zu nehmen sind vorbei. Hat man eine Seite, die schwer zu beschaffende Informationen beinhaltet, so ist der Standardweg, dem Anwender zunächst schon einmal eine Seite auszuliefern, die dann mit Hilfe von asynchronen Requests (AJAX) die restlichen Seitenkomponenten nachlädt und im DOM ersetzt. Mit den aktuellen Frameworks und Bibliotheken ist das alles recht unproblematisch und schnell implementiert. Und so ist man auch in den meisten Fällen mit der Kombination aus synchronen Controller-Actions, Partial Views und AJAX recht gut bedient.

In bestimmten Fällen kann es aber auch sinnvoll sein, serverseitig asynchrone Prozesse zu verwenden. Auch wenn alles recht einfach implementiert ist (wie ich gleich zeigen werde), so ist im Vorab die Sinnhaftigkeit zu hinterfragen. Die Umstellung auf asynchrone Aktionen bringt nur dann etwas, wenn beispielsweise unabhängig voneinander mehrere Backend-Services abgefragt werden und die Backend-Systeme auch in der Lage sind, diese Anfragen parallel zu beantworten. In diesem Fall würde im Vergleich zur sequentiellen Abarbeitung in der sich die Ausführungszeit aus der Summe der Subrequests zusammensetzt die parallele Abarbeitung nur etwas länger als der längste Request dauern.

Das Vorgehen ist nicht kompliziert: Zunächst ändert man die Basisklasse des Controllers. Statt von Controller zu erben, verwendet man nun AsyncController.

public class HomeController : AsyncController
{
     // todo: add some views
}

Auch mit dieser Basisklasse sind nach wie vor synchrone Anfragen möglich - es wird ledigleich zusätzlich die Möglichkeit gegeben, serverseitig asynchrone zu arbeiten.

Der nächste Schritt betrifft die Action. Synchrone Controller-Actions bestehen aus einer Methode, die normalerweise den Namen der View haben und als Rückgabeparameter ein ActionResult haben. Das folgende Beispiel würde die View Index ausliefern.

public ActionResult Index()
{
    // do something
    return View();
}

Im Fall der asynchronen Controller-Action muss diese Methode gesplittet werden: Die erste Methode (im Beispiel IndexAsync) nimmt den Request entgegen und startet die asynchronen Hintergrundprozesse. Diese Methode hat keinen Rückgabewert. Dieser ist erst in der zweiten Methode vorhanden (im Beispiel IndexCompleted). Hier erfolgt die Verarbeitung der Rückgabewerte der Hintergrund-Requests und die Auslieferung der View. Wie häufig im ASP.NET MVC, so ist die konventionsgemäße Benennung der Methoden wichtig, damit die Magie funktioniert.

Wichtig ist, dass man nicht vergessen darf, dass man die Abarbeitung in den Thread-Pool übergibt und es dort ggf. einige Kontext-Objekte nicht mehr gibt und dass man die Kontrolle in einem statischen Kontext zurück bekommt. Zur Unterstützung der Abarbeitung steht im AsyncController ein AsyncManager zur Verfügung. Mit dessen Eigenschaft OutstandingOperations definiert man, wie viele offene Hintergrundprozesse es gibt. Wann immer ein Subrequest ein Ergebnis zurückliefert, schreibt man das Ergebnis in die Parameters-Auflistung des AsyncManagers zurück und verringert die OutstandingOperations.

Mein komplettes Beispiel sieht nun wie folgt aus:

public class HomeController : AsyncController
{

    public void IndexAsync()
    {
        AsyncManager.OutstandingOperations.Increment();
        var webRequest = WebRequest.Create(
                "http://api.twitter.com/1/statuses/user_timeline.xml"
                + "?screen_name=martin_hey");


        Observable
        .FromAsyncPattern<WebResponse>(webRequest.BeginGetResponse, 
            webRequest.EndGetResponse)()
        .Subscribe(response =>
                        {
                            var streamReader = new StreamReader(
                                response.GetResponseStream());
                            var responseText = streamReader.ReadToEnd();
                            AsyncManager.Parameters["TweetXml"] = responseText;
                            AsyncManager.OutstandingOperations.Decrement();
                        });
    }



    public ActionResult IndexCompleted(string tweetXml)
    {
        var model = new TweetViewModel
                        {
                            TimelineXml = tweetXml
                        };
        return View(model);
    }

}

In IndexAsync erfolgt ein Webrequest auf ein Drittsystem - im echten Leben wären hier mehrere Requests. Im der Callback-Action dieses Requests schreibe ich das erhaltene Ergebnis in die Parameter des AsyncManagers. Man könnte hier auch komplette Objekte oder Objektstrukturen ablegen, ich beschränke mich aber der Einfachheit halber auf den XML-String. Ist OutstandingOperations wieder auf 0, so wird IndexCompleted automatisch aufgerufen und die Parameter im AsyncManager auf die Parameter der Methode gemappt. Hier erfolgt dann die restliche Verarbeitung - in der Regel der Aufbau des Models zur Darstellung der View und die Rückgabe der View.

Einen kleinen Nachteil hat diese Umstellung noch: Kann man in der synchronen Verarbeitung problemlos mit Hilfe von Profiler-Werkzeugen (z.B. MVC Mini Profiler) eingreifen und beispielsweise die Laufzeit der Subrequests anzeigen, so wird das im asynchronen Kontext schon komplizierter bis unmöglich.

Englische Version Englische Version

Einladung zum Treffen der .NET Usergroup Dresden am 14.07.2011

Das kommende Treffen der .NET Usergroup Dresden wird am 14.07.2011 bei Saxonia Systems stattfinden. Dabei geht es hauptsächlich um die folgenden Themen:

  1. MEF (Mario Kretschmer)
    Das Microsoft Extensibility Framework ist eine neue Bibliothek im .NET Framwork 4, die es recht leicht macht, erweiterbare Anwendungen zu schreiben. Mit Hilfe weniger Attribute kann man so die Komplexität solcher Anwendungen stark verringern und kann dynamisch Module nachladen (AddIn-Konzept).
  2. BDD mit SpecFlow (Hendrik Lösch)
    SpecFlow versucht, die Lücke zwischen Entwicklern und  Domain-Experten zu schließen. Dazu bietet es einen Editor, in dem man seine Anforderungen an die Domain recht normalsprachlich (im Gherkin-Format) in feature-Dateien herunterschreiben kann. Aus diesen werden dann Testschritte generiert, die in Unittests automatisiert getestet werden können.
Im Anschluss daran gibt es wieder Gelegenheit, gemeinsam zu netzwerken. Nähere Informationen und einen Link zur Anmeldeliste finden sich auf der Webseite der .NET Usergroup Dresden.