Extreme ASP.NET
Routing mit ASP.NET-Web Forms
Scott Allen
Codedownload verfügbar in der MSDN-Codegalerie
Code online durchsuchen
Inhalt
Was ist Routing?
Eine kurze Geschichte vom Neuschreiben von URLs
Von Routen und Routenhandlern
Konfigurieren von ASP.NET für das Routing
Konfigurieren von Routen
Der Rezeptroutenhandler
Routing und Sicherheit
URL-Generierung
Zusammenfassung zu Routen
Mit Service Pack 1 für Microsoft .NET Framework 3.5 wurde ein Routingmodul für die ASP.NET-Laufzeit eingeführt. Mit dem Routingmodul kann die URL in einer eingehenden HTTP-Anforderung vom physischen Web Form entkoppelt werden, das auf die Anforderung reagiert. Dadurch können Sie freundliche URLs für Ihre Webanwendungen erstellen. Obwohl Sie in früheren Versionen von ASP.NET freundliche URLs verwenden konnten, bietet das Routingmodul einen einfacheren, saubereren und testbareren Ansatz.
Das Routingmodul hat seinen Ursprung im ASP.NET-MVC-Framework (Model View Controller), das sich zum Zeitpunkt des Verfassens dieses Artikels in einer Vorschauphase befindet. Microsoft hat die Routinglogik jedoch in die System.Web-Routing-Assembly gepackt und die Assembly mit SP1 veröffentlicht. Mit der Assembly wird derzeit Routing für Websites bereitgestellt, bei denen ASP.NET Dynamic Data-Features verwendet werden (die ebenfalls mit SP1 veröffentlicht wurden). In diesem Artikel wird jedoch die Routingfunktion mit ASP.NET-Web Forms demonstriert.
Was ist Routing?
Angenommen, Sie haben ein ASP.NET-Web Form namens „RecipeDisplay.aspx“, und dieses Formular befindet sich in einem Ordner namens „Web Forms“. Der klassische Ansatz zum Anzeigen eines Rezepts mit diesem Web Form besteht darin, eine URL zu erstellen, die auf den physischen Speicherort des Formulars zeigt. Außerdem werden einige Daten in die Abfragezeichenfolge codiert, mit denen dem Web Form mitgeteilt wird, welches Rezept angezeigt werden soll. Das Ende einer solchen URL sieht möglicherweise wie folgt aus: „/WebForms/RecipeDisplay.aspx?id=5“, wobei mit der Zahl 5 ein Primärschlüsselwert in einer Datenbanktabelle voller Rezepte dargestellt wird.
Beim Routing geht es im Wesentlichen um das Zerlegen eines URL-Endpunkts in Parameter und die anschließende Verwendung dieser Parameter, um die Verarbeitung der HTTP-Anforderung mit einer spezifischen Komponente zu steuern. Nehmen Sie die URL „/recipe/5“ als Beispiel. Mit der richtigen Routingkonfiguration können Sie immer noch mit dem Web Form „RecipeDisplay.aspx“ auf diese URL antworten.
Die URL stellt keinen physischen Pfad mehr dar. Stattdessen stellt das Wort „recipe“ einen Parameter dar, mit dem das Routingmodul nach einer Komponente zum Verarbeiten von Rezeptanforderungen suchen kann. Mit der Zahl 5 wird ein zweiter Parameter dargestellt, den Sie bei der Verarbeitung benötigen, um ein bestimmtes Rezept anzuzeigen. Statt Datenbankschlüssel in die URL zu kodieren, könnte es besser sein, eine URL wie „/recipe/tacos“ zu verwenden. Diese URL enthält nicht nur genügend Parameter, um ein bestimmtes Rezept anzuzeigen, sondern ist auch von Menschen lesbar, offenbart Endbenutzern die zugrunde liegende Absicht und enthält wichtige Schlüsselwörter, die von Suchmaschinen gefunden werden können.
Eine kurze Geschichte vom Neuschreiben von URLs
In ASP.NET erforderte die Verwendung einer URL mit der Endung „/recipe/tacos“ in der Regel die Arbeit mit einem Schema zum Neuschreiben von URLs. Weitere Informationen zum Neuschreiben von URLs finden Sie in dem hervorragenden Artikel Neuschreiben von URLs in ASP.NET von Scott Mitchell. In dem Artikel wird die allgemeine Implementierung des Neuschreibens von URLs in ASP.NET mit einem HTTP-Modul und der statischen RewritePath-Methode der HttpContext-Klasse beschrieben. In dem Artikel von Scott Mitchell werden auch ausführlich die Vorteile von freundlichen, hack-baren URLs geschildert.
Jene von Ihnen, die schon einmal die RewritePath-API verwendet haben, sind wahrscheinlich mit einigen der Marotten und Schwächen im Ansatz des Neuschreibens vertraut. Das primäre Problem bei RewritePath besteht darin, wie die Methode den virtuellen Pfad ändert, der während der Verarbeitung einer Anforderung verwendet wird. Beim Neuschreiben von URLs mussten Sie das Postbackziel jedes Web Form überarbeiten (oft durch das erneute Neuschreiben der URL während der Anforderung), um Postbacks an die interne, neu geschriebene URL zu vermeiden.
Außerdem hätten die meisten Entwickler das Neuschreiben von URLs als unidirektionale Übersetzung implementiert, da es keine einfache Methode gab, die Logik zum Neuschreiben von URLs in zwei Richtungen arbeiten zu lassen. Es war z. B. einfach, der Logik zum Neuschreiben von URLs eine öffentliche URL zu geben und von der Logik die interne URL eines Web Form zurückgeben zu lassen. Es war schwierig, der Neuschreibelogik die interne URL eines Web Form zu geben und von dieser die öffentliche URL zurückgeben zu lassen, die zum Erreichen des Formulars erforderlich war. Letzteres ist beim Generieren von Hyperlinks zu anderen Web Forms nützlich, die sich hinter neu geschriebenen URLs verbergen. Wie Sie im weiteren Verlauf dieses Artikels sehen werden, werden diese Probleme mit dem URL-Routingmodul umgangen.
Abbildung 1 Routen, Routenhandler und das Routingmodul
Von Routen und Routenhandlern
Es gibt drei wesentliche Akteure im URL-Routingmodul: Routen, Routenhandler und das Routingmodul selbst. Durch eine Route wird eine URL einem Routenhandler zugeordnet. Mit einer Instanz der Route-Klasse des System.Web.Routing-Namespace wird eine Route während der Laufzeit repräsentiert, und die Parameter und Einschränkungen der Route werden beschrieben. Ein Routenhandler erbt von der Schnittstelle „System.Web.Routing.IRouteHandler“. Aufgrund dieser Schnittstelle muss der Routenhandler eine GetHttpHandler-Methode implementieren, mit der ein Objekt zurückgegeben wird, mit dem wiederum die IHttpHandler-Schnittstelle implementiert wird. Die IHttpHandler-Schnittstelle war von Anfang an Bestandteil von ASP.NET, und ein Web Form (ein System.Web.UI.Page) ist ein IHttpHandler. Wenn Routing bei Web Forms verwendet wird, muss mit Ihren Routenhandlern nach dem richtigen Web Form gesucht werden, und dieses muss instanziiert und zurückgegeben werden. Schließlich wird das Routingmodul an die ASP.NET-Verarbeitungspipeline angeschlossen. Mit dem Modul werden eingehende Anforderungen abgefangen, die URL untersucht und festgestellt, ob entsprechende Routen definiert sind. Mithilfe des Moduls wird der zugeordnete Routenhandler für eine entsprechende Route abgerufen. Der Routenhandler wird um den IHttpHandler gebeten, mit dem die Anforderung verarbeitet wird.
In Abbildung 1 sind die drei primären Typen gezeigt, die ich erwähnt habe. Im nächsten Abschnitt werde ich diese drei Akteure arbeiten lassen.
Konfigurieren von ASP.NET für das Routing
Um eine ASP.NET-Website oder -Webanwendung für das Routing zu konfigurieren, müssen Sie zuerst einen Verweis auf die System.Web.Routing-Assembly hinzufügen. Mit der Installation von SP1 für das .NET Framework 3.5 wird diese Assembly im globalen Assemblycache installiert. Sie finden die Assembly dann in dem standardmäßigen Dialogfeld „Verweis hinzufügen“.
Außerdem müssen Sie das Routingmodul in die ASP.NET-Pipeline konfigurieren. Das Routingmodul ist ein standardmäßiges HTTP-Modul. Für IIS 6.0 und frühere Versionen sowie für den Visual Studio-Webentwicklungsserver installieren Sie das Modul mithilfe des <httpModules>-Abschnitts der Datei „web.config“, wie Sie hier sehen:
<httpModules>
<add name="RoutingModule"
type="System.Web.Routing.UrlRoutingModule,
System.Web.Routing,
Version=3.5.0.0, Culture=neutral,
PublicKeyToken=31bf3856ad364e35"/>
<!-- ... -->
</httpModules>
Um in IIS 7.0 eine Website mit Routing auszuführen, benötigen Sie zwei Einträge in web.config. Der erste Eintrag ist die Konfiguration des URL-Routingmoduls, die sich im <modules>-Abschnitt von <system.webServer> befindet. Außerdem benötigen Sie im <handlers>-Abschnitt von <system.webServer> einen Eintrag, um Anforderungen für UrlRouting.axd verarbeiten zu können. Diese beiden Einträge sind in Abbildung 2 zu sehen. Weitere Informationen finden Sie in der Randleiste „Konfigurationseinträge für IIS 7.0“.
Abbildung 2 Konfiguration des URL-Routingmoduls
<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>
Sobald Sie das URL-Routingmodul in die Pipeline konfiguriert haben, schreibt es sich selbst in die PostResolveRequestCache- und PostMapRequestHandler-Ereignisse. In Abbildung 3 wird eine Teilmenge von Pipelineereignissen angezeigt. Implementierungen des Neuschreibens von URLs führen ihre Arbeit in der Regel während des BeginRequest-Ereignisses durch. Dies ist das früheste Ereignis, das während einer Anforderung ausgelöst wird. Beim URL-Routing finden der Routenabgleich und die Auswahl eines Routenhandlers während der PostResolveRequestCache-Phase statt, also nach den Authentifizierungs-, Autorisierungs- und Cachesuchphasen der Verarbeitung. Weiter unten in diesem Artikel wird erneut auf die Implikationen der zeitlichen Steuerung dieses Ereignisses eingegangen.
Abbildung 3 HTTP-Anforderung
Konfigurieren von Routen
Routen und Routenhandler gehen Hand in Hand, aber zunächst wird der Code zum Konfigurieren von Routen vorgestellt. Mit der RouteTable-Klasse des Routingmoduls wird eine RouteCollection über deren statische Routes-Eigenschaft verfügbar gemacht. Sie müssen alle Ihre benutzerdefinierten Routen in diese Sammlung konfigurieren, bevor die Anwendung beginnt, die erste Anforderung auszuführen. Das bedeutet, dass Sie eine global.asax-Datei und das Application_Start-Ereignis verwenden müssen.
In Abbildung 4 wird der Routenregistrierungscode gezeigt, den Sie für „/recipe/brownies“ verwenden müssen, um das Web Form „RecipeDisplay.aspx“ zu erreichen. Die Parameter für die Add-Methode der RouteCollection-Klasse enthalten einen Anzeigenamen für die Route, gefolgt von der Route selbst. Der erste Parameter zum Route-Konstruktor ist ein URL-Muster. Das Muster besteht aus den URL-Segmenten, die am Ende einer URL stehen, mit der auf diese Anwendung gezeigt wird (nach allen Segmenten, die erforderlich sind, um den Stamm der Anwendung zu erreichen). Für eine Anwendung mit dem Stamm „localhost/food/“ entspricht das Routenmuster in Abbildung 4 dann „localhost/food/recipe/brownies“.
Abbildung 4 Routenregistrierungscode für „/recipe/brownies“
protected void Application_Start(object sender, EventArgs e)
{
RegisterRoutes();
}
private static void RegisterRoutes()
{
RouteTable.Routes.Add(
"Recipe",
new Route("recipe/{name}",
new RecipeRouteHandler(
"~/WebForms/RecipeDisplay.aspx")));
}
Konfigurationseinträge für IIS 7.0
Das runAllManagedModulesForAllRequests-Attribut erfordert den Wert „true“, wenn Sie die erweiterungslosen URLs wie in diesem Beispiel verwenden möchten. Es erscheint u. U. seltsam, einen HTTP-Handler für UrlRouting.axd zu konfigurieren. Dies ist eine kleine Problemumgehung, die für das Routingmodul erforderlich ist, damit Routing unter IIS 7.0 funktioniert. Mit dem UrlRouting-Modul wird die neu eingehende URL tatsächlich in „~/UrlRouting.axd“ umgeschrieben, wodurch die URL wieder in die ursprüngliche, eingehende URL umgeschrieben wird. Wahrscheinlich wird sich eine zukünftige Version von IIS perfekt in das Routingmodul integrieren lassen und diese Problemumgehung nicht erfordern.
Mit Segmenten in geschweiften Klammern werden Parameter gekennzeichnet. Mit dem Routingmodul werden die Werte automatisch extrahiert und in einem Name/Wert-Wörterbuch platziert, das für die Dauer der Anforderung existiert. In dem bereits angeführten Beispiel für „localhost/food/recipe/brownies“ wird mit dem Routingmodul der Wert „brownies“ extrahiert und im Wörterbuch mit einem Schlüssel „name“ gespeichert. Sie werden sehen, wie das Wörterbuch verwendet wird, wenn Sie sich den Code für den Routenhandler ansehen.
Sie können beliebig viele Routen zu RouteTable hinzufügen, aber die Anordnung der Routen ist wichtig. Mit dem Routingmodul werden alle eingehenden URLs in der Reihenfolge in Bezug auf die Routen in der Sammlung getestet, in der sie erscheinen. Die erste Route mit einem entsprechenden Muster wird vom Modul ausgewählt. Aus diesem Grund sollten Sie die spezifischsten Routen zuerst hinzufügen. Wenn Sie eine generische Route mit dem URL-Muster „{Kategorie}/{Unterkategorie}“ vor der Rezeptroute hinzugefügt haben, würde die Rezeptroute nie vom Routingmodul gefunden werden. Ein zusätzlicher Hinweis: Beim Mustervergleich im Routingmodul wird die Groß-/Kleinschreibung nicht berücksichtigt.
Mit überlasteten Versionen des Route-Konstruktors können Sie standardmäßige Parameterwerte erstellen und Einschränkungen anwenden. Anhand von Standards können Sie Standardwerte für das Routingmodul angeben, die in das Wörterbuch „name/value parameter“ eingegeben werden, wenn für den Parameter in einer eingehenden URL kein Wert vorhanden ist. Beispielsweise können Sie „brownies“ zum Standardrezeptnamen machen, wenn vom Routingmodul eine Rezept-URL ohne Namenswert (wie „localhost/food/recipe“) festgestellt wird.
Mithilfe von Einschränkungen können Sie reguläre Ausdrücke angeben, um Parameter zu überprüfen und den Routenmustervergleich bei eingehenden URLs fein abzustimmen. Wenn Sie Primärschlüsselwerte verwendet haben, um Rezepte in einer URL zu identifizieren (wie „localhost/food/recipe/5“), können Sie mit einem regulären Ausdruck sicherstellen, dass der Primärschlüsselwert in der URL eine ganze Zahl ist. Sie können Einschränkungen auch mithilfe eines Objekts anwenden, mit dem die IRouteConstraint-Schnittstelle implementiert wird.
Der zweite Parameter im Route-Konstruktor ist eine neue Instanz des Routenhandlers, der in Abbildung 5 veranschaulicht wird.
Abbildung 5 RecipeRouteHandler
public class RecipeRouteHandler : IRouteHandler
{
public RecipeRouteHandler(string virtualPath)
{
_virtualPath = virtualPath;
}
public IHttpHandler GetHttpHandler(RequestContext requestContext)
{
var display = BuildManager.CreateInstanceFromVirtualPath(
_virtualPath, typeof(Page)) as IRecipeDisplay;
display.RecipeName = requestContext.RouteData.Values["name"] as string;
return display;
}
string _virtualPath;
}
Der Rezeptroutenhandler
Anhand des folgenden Codeausschnitts wird eine grundlegende Implementierung eines Routenhandlers für Rezeptanforderungen gezeigt. Da mit dem Routenhandler letzten Endes eine Instanz eines IHttpHandler (in diesem Fall RecipeDisplay.aspx) erstellt werden muss, ist für den Konstruktor ein virtueller Pfad erforderlich, mit dem auf das Web Form gezeigt wird, das vom Routenhandler erstellt wird. Mit der GetHttpHandler-Methode wird dieser virtuelle Pfad an ASP.NET-BuildManager übergeben, um das instanziierte Web Form abzurufen:
interface IRecipeDisplay : IHttpHandler
{
string RecipeName { get; set; }
}
Mit dem Routenhandler können auch Daten aus dem Parameterwörterbuch des Routingmoduls gezogen werden, wobei es sich um die RouteData-Eigenschaft der RequestContext-Klasse handelt. Mit dem Routingmodul wird RequestContext eingerichtet und eine Instanz übergeben, wenn diese Methode aufgerufen wird. Es gibt viele Optionen, um die Routendaten ins Web Form zu bekommen. Sie können die Routendaten z. B. in der HttpContext-Items-Sammlung übergeben. In diesem Beispiel haben Sie eine Schnittstelle für Ihr zu implementierendes Web Form definiert (IRecipeDisplay). Mit dem Routenhandler können im Web Form stark typisierte Eigenschaften festgelegt werden, um alle Informationen zu übergeben, die für das Web Form erforderlich sind. Dieser Ansatz funktioniert sowohl mit der ASP.NET-Website als auch mit den ASP.NET-Anwendungskompilierungsmodellen.
Routing und Sicherheit
Wenn Sie ASP.NET-Routing verwenden, können Sie immer noch alle ASP.NET-Features verwenden, die Sie zu schätzen gelernt haben: Masterseiten, Ausgabezwischenspeicherung, Themen, Benutzersteuerelemente und mehr. Es gibt jedoch eine beachtenswerte Ausnahme. Das Tolle ist, dass vom Routingmodul Ereignisse in der Pipeline verwendet werden, die nach den Authentifizierungs- und Autorisierungsphasen der Verarbeitung stattfinden. Das bedeutet, dass Ihre Benutzer von ASP.NET autorisiert werden, die öffentliche, sichtbare URL zu verwenden, und nicht den virtuellen Pfad zum ASP.NET-Web Form, der vom Routenhandler ausgewählt wird, um die Anforderung zu verarbeiten. Sie müssen der Autorisierungsstrategie für eine Anwendung, bei der Routing verwendet wird, besondere Aufmerksamkeit widmen.
Angenommen, Sie möchten nur authentifizierten Benutzern ermöglichen, Rezepte anzuzeigen. Ein Ansatz bestünde darin, die web.config-Stammdatei dahingehend zu verändern, dass Autorisierungseinstellungen verwendet werden:
<location path="recipe">
<system.web>
<authorization>
<deny users="?"/>
</authorization>
</system.web>
</location>
Durch diesen Ansatz wird zwar verhindert, dass anonyme Benutzer „/recipe/tacos“ anzeigen, aber er hat zwei grundlegende Schwächen. Erstens wird mit der Einstellung nicht verhindert, dass ein Benutzer „/WebForms/RecipeDisplay.aspx“ direkt anfordert (obwohl Sie eine weitere Autorisierungsregel hinzufügen können, mit der alle Benutzer daran gehindert werden, Ressourcen vom Web Forms-Ordner anzufordern). Zweitens ist es einfach, die Routenkonfiguration in „global.asax.cs“ zu ändern, ohne die Autorisierungsregeln zu ändern und Ihre geheimen Rezepte für anonyme Benutzern offenzulegen.
Ein alternativer Ansatz zur Autorisierung besteht darin, das Web Form „RecipeDisplay.aspx“ auf Grundlage seines physischen Speicherorts zu schützen. Dabei werden die web.config-Dateien mit <authorization>-Einstellungen direkt in den geschützten Ordner platziert. Da ASP.NET Benutzer jedoch auf Grundlage der öffentlichen URL autorisiert, müssen Sie die Autorisierungsüberprüfungen manuell an dem virtuellen Pfad vornehmen, der von Ihrem Routenhandler verwendet wird.
Sie müssen am Anfang der GetHttpHandler-Methode des Routenhandlers folgenden Code hinzufügen. Durch diesen Code wird die statische CheckUrlAccessForPrincipal-Methode der UrlAuthorizationModule-Klasse verwendet (das gleiche Modul, mit dem Autorisierungsüberprüfungen in der ASP.NET-Pipeline durchgeführt werden):
if (!UrlAuthorizationModule.CheckUrlAccessForPrincipal(
_virtualPath, requestContext.HttpContext.User,
requestContext.HttpContext.Request.HttpMethod))
{
requestContext.HttpContext.Response.StatusCode =
(int)HttpStatusCode.Unauthorized;
requestContext.HttpContext.Response.End();
}
Um über RequestContext auf die HttpContext-Mitglieder zuzugreifen, müssen Sie der System.Web.Abstractions-Assembly einen Verweis hinzufügen.
Mit einem sicheren Routenhandler können Sie jetzt Ihre Aufmerksamkeit der Seite widmen, mit der für jedes Rezept in Ihrer Datenbank Hyperlinks generiert werden müssen. Wie sich herausstellt, kann die Routinglogik Ihnen auch dabei helfen, diese Seite zu erstellen.
URL-Generierung
Um den Hyperlink zu einem beliebigen gegebenen Rezept zu generieren, wird noch einmal die Sammlung der Routen zu Rate gezogen, die während des Anwendungsstarts konfiguriert werden. Wie hier gezeigt, hat die RouteCollection-Klasse eine GetVirtualPath-Methode für diesen Zweck:
VirtualPathData pathData =
RouteTable.Routes.GetVirtualPath(
null,
"Recipe",
new RouteValueDictionary { { "Name", recipeName } });
return pathData.VirtualPath;
Sie müssen den gewünschten Routennamen („Recipe“) zusammen mit einem Wörterbuch der erforderlichen Parameter und ihrer zugeordneten Werte übermitteln. Bei dieser Methode wird das URL-Muster verwendet, das Sie zuvor erstellt haben („/recipe/{Name}“), um die richtige URL zu erstellen.
Diese Methode wird mit dem folgenden Code verwendet, um eine Sammlung anonym typisierter Objekte zu generieren. Die Objekte haben Namen und URL-Eigenschaften, die Sie mit Datenbindung verwenden können, um eine Liste oder Tabelle verfügbarer Rezepte zu generieren:
var recipes =
new RecipeRepository()
.GetAllRecipeNames()
.OrderBy(recipeName => recipeName)
.Select(recipeName =>
new
{
Name = recipeName,
Url = GetVirtualPathForRecipe(recipeName)
});
Die Möglichkeit, URLs in Ihrer Routingkonfiguration zu generieren, bedeutet, dass Sie die Konfiguration ändern können, ohne Angst vor dem Erstellen fehlerhafter Links innerhalb Ihrer Anwendung haben zu müssen. Natürlich können Sie u. U. immer noch die bevorzugten Links und Favoriten Ihrer Benutzer zerstören, aber die Möglichkeit der Änderung ist ein ungeheurer Vorteil, wenn Sie immer noch die URL-Struktur der Anwendung entwerfen.
Zusammenfassung zu Routen
Mit dem URL-Routingmodul wird die ganze Arbeit des URL-Mustervergleichs und der URL-Generierung erledigt. Sie müssen lediglich Ihre Routen konfigurieren und Ihre Routenhandler implementieren. Durch Routing sind Sie wirklich von Dateierweiterungen und dem physischen Layout Ihres Dateisystems isoliert, und Sie müssen sich nicht mit einem Tool zum Neuschreiben von URLs herumplagen. Stattdessen können Sie sich auf den optimalen URL-Entwurf für Ihre Endbenutzer und für Suchmaschinen konzentrieren. Außerdem arbeitet Microsoft daran, URL-Routing mit Web Forms noch einfacher und konfigurierbarer als im bevorstehenden ASP.NET 4.0 zu machen.
Senden Sie Fragen und Kommentare in englischer Sprache an xtrmasp@microsoft.com.
Scott Allen ist Mitgründer von OdeToCode und technischer Mitarbeiter bei Pluralsight. Sie können Scott Allen unter scott@odetocode.com erreichen oder seinen Blog unter OdeToCode.com/blogs/scott lesen.