Archiv für Oktober 2009
Routing mit ASP.NET WebForms, Teil 2
Donnerstag, 29. Oktober 2009In Teil 1 ging es darum, wie die ASP.NET 3.5 Routing Engine in einer WebForms Applikation genutzt werden kann. Angeschaut wurde die Konfiguration der Applikation für die Engine, und das Definieren und Auswerten eigener Routen.
In diesem zweiten Teil werden nun die Security Aspekte angeschaut.
Das Routing Modul verarbeitet die Events in der Request Pipeline nachdem die Authentisierung und Autorisierung bereits stattgefunden hat. Das bedeutet, dass die Benutzer anhand der sichtbaren URL autorisiert werden und nicht über den virtuellen Pfad zum WebForm.
Wenn also der Zugriff auf unsere Beispiel URL “customer/” nur authentisierten Benutzern gestattet sein soll, kann dies über einen Eintrag im Root web.config erreicht werden:
<location path="customer">
<system.web>
<authorization>
<deny users="?" />
</authorization>
</system.web>
</location>
Anonyme Benutzer der Applikation können nun nicht mehr auf Adressen wie “customer” oder “customer/42″ zugreifen, sondern werden auf eine Login Seite umgeleitet. Diese enthält sogar den Pfad unserer Route:
Nun muss noch sichergestellt werden, dass die Authorisation Prüfung über den virtuellen Pfad erfolgt, den der RouteHandler verwendet. Die Methode GetHttpHandler sieht also wie folgt aus:
public IHttpHandler GetHttpHandler(RequestContext requestContext)
{
if (!UrlAuthorizationModule.CheckUrlAccessForPrincipal(_virtualPath, requestContext.HttpContext.User, requestContext.HttpContext.Request.HttpMethod))
{
requestContext.HttpContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
requestContext.HttpContext.Response.End();
}
var page = BuildManager.CreateInstanceFromVirtualPath(_virtualPath, typeof(Page));
var queryString = new StringBuilder("?");
foreach (var rdv in requestContext.RouteData.Values)
{
queryString.Append(requestContext.HttpContext.Server.UrlEncode(rdv.Key));
queryString.Append("=");
queryString.Append(requestContext.HttpContext.Server.UrlEncode(rdv.Value.ToString()));
queryString.Append("&");
}
queryString.Remove(queryString.Length - 1, 1);
HttpContext.Current.RewritePath(string.Concat(_virtualPath, queryString));
return (IHttpHandler)page;
}
Eine letzte Einstellung ist noch nötig. Anonyme Benutzer können immer noch direkt über den physikalischen Pfad (z.B. ~/Customers/CustomerDetail.aspx) auf die WebForms zugreifen. Dies kann verhindert werden, indem eine web.config Datei im entsprechenden Verzeichnis angelegt wird:
<configuration>
<system.web>
<authorization>
<deny users="?" />
</authorization>
</system.web>
</configuration>
Ich habe ein Beispielprojekt zusammengestellt, welches alle besprochenen Elemente zeigt: RoutingWebApplication.zip
Routing mit ASP.NET WebForms, Teil 1
Mittwoch, 28. Oktober 2009Einer der Gründe, weshalb ich das ASP.NET MVC Framework mag, ist die durchgehende Verwendung der sehr flexiblen Routing Engine. In einem MVC Projekt ist das Addressierungsschema komplett vom physikalischen Filesystem getrennt. So lassen sich einfach Applikationen im REST Stil bauen. Adressen wie “http://applikation/Kunde/42″ oder “http://applikation/Kunde/42/Bestellungen” sind auch deutlich SEO freundlicher als die direkte Adressierung auf ein physikalisch vorhandenes File.
Die Routing Engine ist jedoch nicht Bestandteil des MVC Frameworks, sondern wurde mit .NET 3.5 SP1 eingeführt. Somit kann sie auch in einem WebForm Projekt verwendet werden. Der Aufwand hält sich dabei in Grenzen, das ganze ist recht schnell umgesetzt.
Teil 1 dieses Artikels zeigt die Konfiguration der Routing Engine in einer WebForms Applikation und das Anlegen eigener Routen. In einem zweiten Teil werden dann die Security Einstellungen betrachtet.
Konfiguration der WebForms Applikation
Um die Routing Engine nutzen zu können, müssen im Projekt zwei Verweise gesetzt werden: System.Web.Routing und System.Web.Abstractions sind beide im GAC installiert, sie können einfach über “Add Reference” hinzugefügt werden.
Danach muss das Routing Modul im web.config in die Request Pipeline konfiguriert werden. Unter IIS 6 geschieht dies wie folgt:
<httpModules>
...
<add name="RoutingModule"
type="System.Web.Routing.UrlRoutingModule, System.Web.Routing, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
Wenn IIS 7 verwendet wird, müssen die folgenden Einträge gemacht werden:
<system.webServer>
<modules runAllManagedModulesForAllRequests="true">
...
<add name="UrlRoutingModule"
type="System.Web.Routing.UrlRoutingModule, System.Web.Routing, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35&" />
...
</modules>
<handlers>
...
<add name="UrlRoutingHandler"
preCondition="integratedMode"
verb="*" path="UrlRouting.axd"
type="System.Web.HttpForbiddenHandler, System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
...
</handlers>
</system.webServer>
Wenn die Zielumgebung bekannt ist, kann auf die eine Variante verzichtet werden, Einträge für IIS 6 werden allerdings vom IIS 7 ignoriert, und umgekehrt auch, so dass getrost beide Varianten eingetragen werden können.
Konfiguration der Routen
Falls nicht bereits vorhanden, wird eine global.asax Datei zum Projekt hinzugefügt. In dessen Application_Start Event können die Routen eingetragen werden. Eine Route hat immer einen eindeutigen Namen, definiert ein Adressenmuster und legt fest, von welchem Handler Anfragen an diese Adresse bearbeitet werden:
RouteTable.Routes.Add("Customers", new Route("customer/", new PageRouteHandler("~/Customers/Customers.aspx")));
RouteTable.Routes.Add("CustomerDetails", new Route("customer/{id}", new PageRouteHandler("~/Customers/CustomerDetails.aspx")));
Dies erstellt zwei Einträge in der Routing Tabelle. Der zweite Eintrag verwendet einen Parameter “id”, wenn also ein Request in der Form “customer/42″ eintrifft, wird dieser Eintrag verwendet.
Zu beachten ist, dass beim Eintreffen eines Requests diese Routing Tabelle von oben nach unten abgearbeitet wird, und der erste zur Request Adresse passende Eintrag verwendet wird. Nachfolgende Einträge werden ignoriert.
Nun muss der PageRouteHandler angelegt werden, welcher diese Anfragen bearbeiten kann. Dies ist eine Klasse, welche das IRouteHandler Interface implementiert:
public class PageRouteHandler : IRouteHandler
{
private readonly string _virtualPath;
public PageRouteHandler(string virtualPath)
{
_virtualPath = virtualPath;
}
public IHttpHandler GetHttpHandler(RequestContext requestContext)
{
var page = BuildManager.CreateInstanceFromVirtualPath(_virtualPath, typeof(Page));
return (IHttpHandler)page;
}
}
Dies ist bereits alles, was benötigt wird, um eigene Routen zu definieren und an Webforms weiterzuleiten. Jedoch gelangt im zweiten Fall, wenn eine ID übergeben wird, diese nicht bis zum Webform. Dies kann gelöst werden, indem innerhalb des Routing Handlers die Routing Daten des RequestContext an das Webform weitergeleitet werden. Die Methode GetHttpHandler sieht also wie folgt aus:
public IHttpHandler GetHttpHandler(RequestContext requestContext)
{
var page = BuildManager.CreateInstanceFromVirtualPath(_virtualPath, typeof(Page));
var queryString = new StringBuilder("?");
foreach (var rdv in requestContext.RouteData.Values)
{
queryString.Append(requestContext.HttpContext.Server.UrlEncode(rdv.Key));
queryString.Append("=");
queryString.Append(requestContext.HttpContext.Server.UrlEncode(rdv.Value.ToString()));
queryString.Append("&");
}
queryString.Remove(queryString.Length - 1, 1);
HttpContext.Current.RewritePath(string.Concat(_virtualPath, queryString));
return (IHttpHandler)page;
}
Neue UI Komponente DropDownListEx für ASP.NET MVC
Montag, 26. Oktober 2009Ein den meisten (MS) Entwicklern unbekanntes HTML Tag ist das <optgroup> Element. Unter Umständen liegt dies daran, dass das Element in ASP.NET nicht direkt durch ein Server Control unterstützt wird.
Das <optgroup> Element kann innerhalb von Dropdown Listen verwendet werden, um die einzelnen Optionen zu gliedern. So lässt sich einfach eine zweistufige Hierarchie darstellen, HTML konform.
Das Markup für eine solche Dropdown Liste wird wie folgt definiert:
<select id="GameConsole" name="GameConsole"> <option value="">Select your favorite...</option> <optgroup label="Microsoft"> <option value="1">XBox</option> <option value="2">XBox 360</option> </optgroup> <optgroup label="Nintendo"> <option value="3">Wii</option> <option value="4">Gameboy</option> </optgroup> <optgroup label="Sega"> <option value="5">Dreamcast</option> </optgroup> <optgroup label="Sony"> <option value="6">Playstation</option> <option value="7">Playstation 2</option> <option value="8">Playstation 3</option> <option value="9">PSP</option> </optgroup> </select>
Ich habe eine neue HTML Helper Komponente DropDownListEx in meine Library etcetera.Mvc aufgenommen. Diese unterstützt das eben beschriebene Tag.
Dazu habe ich auch eine Hilfsklasse OptionGroupSelectList erstellt, welche eine ähnliche Aufgabe wie die SelectList erfüllt. Die DropDownListEx erwartet im Konstruktor eine solche OptionGroupSelectList. Diese kann vom Controller wie folgt erstellt werden:
GameConsoleViewModel gcvm = new GameConsoleViewModel();
gcvm.GameConsoles = new OptionGroupSelectList<GameConsole>(gameConsoles,
x => x.Id,
x => x.Name,
x => x.Manufacturer.Name);
ViewData.Model = gcvm;
Die drei Lambda Expressions geben die jeweiligen Properties des Aufzählungstyps für das Wert und Namensfeld der Optionen, sowie das Label der Option Group an.
Im View kann der HTML Helper wie folgt aufgerufen werden:
<%= Html.DropDownListEx("GameConsole", Model.GameConsoles, "Select your favorite...") %>
Ich habe noch keinen Release erstellt, die Komponente ist bislang nur als Source Code verfügbar. Ich werde aber bald einen Release erstellen, welcher auch noch weitere Komponenten enthalten wird.
etcetera.Mvc: Meine MVC Library
Freitag, 23. Oktober 2009Ich mag das ASP.NET MVC Framework. Wenn ich die Wahl habe, ziehe ich ein MVC Projekt mittlerweile einem WebForms basierten vor. Die offene Architektur, Flexibilität, Testbarkeit und vor allem die Erweiterbarkeit machen es zu einer Freude Webprojekte zu entwickeln.
Wer mit dem MVC Framework beginnt, wird unweigerlich auf das MvcContrib Projekt stossen. Dieses erweitert das Framework an allen Ecken und Enden.
Gerade zu Beginn wird man die ASP.NET Server Controls vermissen. Die mitgelieferten HTML Helper sind zwar sehr komfortabel, decken aber nur das allernötigste ab. Unterstützung für komplexere GUI Elemente wie ein Grid sucht man vergebens.
Das MvcContrib Projekt enthält nebst vielen nützlichen Dingen wie alternativen ViewEngines, Controller Factories und Routing Unterstützung auch einen grossen Satz HTML Helper. Die mächtigste UI Komponente ist in meinen Augen das Grid.
In meinen MVC Projekten habe ich nach anfänglichen Rumspielen mit jQuery Plugins für tabellarische Daten ausschliesslich das MvcContrib Grid verwendet, habe aber bald begonnen, dieses so zu erweitern, dass auch AJAX basiertes Paging und ein Sortieren der Spalten in einem HTML Helper gekapselt wurden.
Das ganze hat sich zu einer kleinen Library gemausert, die ich nun auf CodePlex als Open Source Projekt veröffentlicht habe. Das Projekt nennt sich etcetera.Mvc und ist in einer ersten Version 0.1 verfügbar.
Momentan sind drei UI Komponenten enthalten:
Grid
Das Grid basiert auf demjenigen von MvcContrib. Dazu gehört eine separate und flexibel konfigurierbare Toolbar, welche für das Paging eingesetzt werden kann. Das Grid verwendet den selben Syntax wie MvcContrib undfügt folgendes hinzu:
- Effizientes asynchrones Paging via AJAX ohne eine einzige Zeile eigenem JavaScript
- Falls der Client kein JavaScript unterstützt, werden regulare Seiten Requests ausgeführt.
- Sortierbare Spalten
- Optionaler Grid Header welcher das Grid ein- und ausfahren kann
- Toolbar für das Paging, mit Anzeige und direkter Wahl der Seitennummer
- Die Toolbar kann einfach und beliebig auch mit eigenen Buttons erweitert werden
- Konfigurierbarer Export der Grid Daten nach Excel
- Alle Labels und Tooltips können selber definiert werden
Das Grid enthält ein jQuery Plugin, die Beispiel Icons und Stylesheets des Screenshots.
Kalender
Der Kalender zeigt eine Monatsansicht und unterstützt die Anzeige von Einträgen wie Meetings. Die Kalender Datenstrukturen sind komplett unabhängig von eigenen Projekten. Der Kalender unterstützt folgende Einstellungen:
- Den Wochentag, an dem die Woche beginnt. In den USA ist dies z.B. der Sonntag.
- Anzeige der Tage des Vor- und Folgemonats
- Highlighting des aktuellen Tags
- Komplette Kontrolle über die Links zu Einträgen oder Daten
Wie die anderen UI Komponenten lässt sich das Aussehen komplett über Stylesheets definieren.
Progressbar
Die Progressbar ist ein sehr simples Element. Ich habe es hauptsächlich dafür benötigt, um die Progressbar von jQuery-UI zu ersetzen.
Weitere Komponenten und Framework Erweiterungen werden folgen.
Fehler im Build Prozess
Dienstag, 20. Oktober 2009Ich war etwas voreilig mit meiner Anpassung des Build Prozesses. Ich wollte für einen Release Build automatisch alle relevanten Files in ein definiertes Verzeichnis kopieren. Dies funktionierte grundsätzlich auch sehr gut, nur hatte ich das Projekt noch nicht in ein Subversion Repository aufgenommen.
Sobald dies aber der Fall ist, fügt Subversion jede Menge eigene Dateien in die lokale Arbeitskopie ein. Diese befinden sich immer in versteckten .svn Verzeichnissen und enthalten Informationen über die lokale Arbeitskopie.
Wie kann man nun das Kopieren dieser Dateien verhindern? Glücklicherweise ist das ItemGroup Element in der Lage mittels des Exclude Attributs einen Filter für Dateien anzugeben:
<ItemGroup>
<EtcDll Include="$(MSBuildProjectDirectory)\bin\Release\etcetera.Mvc.dll" />
<MvcContrib Include="$(MSBuildProjectDirectory)\bin\Release\mvccontrib.dll" />
<MvcContribLic Include="$(MSBuildProjectDirectory)\..\..\lib\MvcContrib\License.txt" />
<Css Include="$(MSBuildProjectDirectory)\UI\css\**\*.*"
Exclude="$(MSBuildProjectDirectory)\UI\css\.svn\**\*.*" />
<Images Include="$(MSBuildProjectDirectory)\UI\images\etcetera\**\*.*"
Exclude="$(MSBuildProjectDirectory)\UI\images\etcetera\.svn\**\*.*" />
<Scripts Include="$(MSBuildProjectDirectory)\UI\scripts\**\*.*"
Exclude="$(MSBuildProjectDirectory)\UI\scripts\.svn\**\*.*" />
<ZipFiles Include="$(MSBuildProjectDirectory)\Release\**\*.*" />
</ItemGroup>
Der Filter unterstützt auch Wildcards und kann auch für das Include Attribut angewendet werden.
Informationen zum eigentlichen Projekt folgen nächstens
.





