ASP.NET 5

Die ASP.NET 5 Runtime im Detail

Daniel Roth

Im letzten November kündigte Microsoft ASP.NET 5 als neues, quelloffenes und plattformübergreifendes Framework für das Erstellen moderner Web- und Cloud-Anwendungen mit dem Microsoft .NET Framework vor. Wir (d.h. das ASP.NET-Entwicklungsteam, dem ich angehöre) haben die ASP.NET 5 Preview zusammen mit der Visual Studio 2015 Preview auf der Connect(); in New York City veröffentlicht. Ich habe Ihnen die ASP.NET 5 Runtime und ihr neues Visual Studio-Projektsystem im Artikel „Vorstellung der ASP.NET 5 Preview“ in der Sonderausgabe des MSDN Magazine vom 15. Dezember 2014 (bit.ly/1K4PY4U) vorgestellt. Seit diesem letzten Blick auf ASP.NET 5 im Dezember hat es zwei weitere Vorabversionen von ASP.NET 5 gegeben: Beta2 mit Visual Studio 2015 CTP 5 und Beta3 mit Visual Studio 2015 CTP 6. Mit jeder Version entwickelt sich das Framework weiter und verbessert sich seine Funktion. In vielen Fällen sind diese Verbesserungen auf großzügige Beiträge aus der .NET-Community mithilfe des öffentlichen ASP.NET-Projekts auf GitHub (bit.ly/1DaY7Cd) zurückzuführen. In diesem Artikel werfe ich einen tieferen Blick unter die Haube der neuen ASP.NET 5 Runtime, um herauszufinden, was sich in der aktuellen Version geändert hat.

Das K Runtime Environment (KRE)

Wie Sie damals im Dezember erfahren haben, baut ASP.NET 5 auf einem flexiblen, plattformübergreifenden Host auf, der eine von mehreren .NET CLRs ausführen kann. Sie können Ihre ASP.NET 5-Anwendungen im .NET Framework mit dessen gesamtem Arsenal an APIs ausführen, um maximale Kompatibilität zu erzielen. Sie können ASP.NET 5 jedoch auch im neuen .NET Core ausführen, wodurch echte parallele Bereitstellungen möglich werden, die Sie in vorhandene Umgebungen kopieren können, ohne irgendwelche sonstigen Änderungen am Computer vornehmen zu müssen. Zukünftig wird es auch möglich sein, ASP.NET 5 plattformübergreifend in .NET Core auszuführen, und schon heute gibt es Communitysupport für die plattformübergreifende Ausführung auf Mono.

Die Hostinginfrastruktur der Runtime für ASP.NET 5 wird aktuell als K Runtime Environment (KRE) bezeichnet. Dabei handelt es sich um einen allgemeinen Platzhalter bis zur Entscheidung für den endgültigen Namen. Die KRE stellt eine Umgebung mit allem bereit, was eine .NET-App für die Ausführung benötigt: einen Hostprozess, CLR-Hostinglogik, Erkennung von verwalteten Einstiegspunkten usw. Die KRE wurde für die Ausführung von plattformübergreifenden .NET-Webanwendungen entwickelt, kann aber auch andere Arten von .NET-Anwendungen, wie etwa Konsolenanwendungen, ausführen. Die KRE basiert auf der gleichen .NET-CLR und den gleichen Basisklassenbibliotheken, die .NET-Entwickler schon lange kennen und schätzen, bietet aber zugleich Unterstützung für die plattformübergreifende Ausführung von .NET-Anwendungen unter Windows, OS X und Linux.

Logisch gesehen weist die KRE fünf Funktionalitätsebenen auf. Ich beschreibe jede dieser Ebenen und ihre Zuständigkeiten.

Ebene 1. Systemeigener Prozess: Der systemeigene Prozess ist eine sehr dünne Schicht, die dafür zuständig ist, den systemeigenen CLR-Host zu finden und aufzurufen und die dem Prozess übergebenen Argumente weiterzugeben, die vom restlichen Stapel verwendet werden sollen. Unter Windows ist das eine systemeigene Programmdatei („klr.exe“), auf dem Mac und unter Linux handelt es sich um ein ausführbares BASH-Skript. Die Ausführung auf IIS wird entweder über ein neues systemeigenes HTTP-Modul oder mithilfe des Helios-Laders erreicht. Der Helios-Lader nutzt die Erweiterungs-Hooks in .NET Framework 4.5.1, um das Bootstrappen der KRE zu ermöglichen, ohne die Installation neuer IIS-Module zu erfordern. Mit dem systemeigenen HTTP-Modul können Sie auf .NET Core basierende Web-Apps auf IIS ohne Abhängigkeit vom .NET Framework ausführen. Das systemeigene HTTP-Modul ist zurzeit noch nicht öffentlich zugänglich, wir haben aber die Absicht, es in einer kommenden Vorschau zur Verfügung zu stellen.

Ebenen 2 und 3. Systemeigener CLR-Host und CLR: Der systemeigene CLR-Host hat im Wesentlichen drei Zuständigkeiten:

  1. Starten der CLR. Wie das geschieht, hängt von der Version der CLR ab. Beispielsweise umfasst das Starten von .NET Core das Laden von „coreclr.dll“, das Konfigurieren und Starten der Runtime und das Erstellen der AppDomain, in der der gesamte verwaltete Code ausgeführt wird. Bei Mono und dem .NET Framework 4.5.1 unterscheidet sich der Vorgang etwas, das Ergebnis ist jedoch das gleiche.
  2. Aufrufen des verwalteten Einstiegspunkts, der die nächste Ebene bildet.
  3. Wenn der Einstiegspunkt des systemeigenen Hosts zurückgegeben wird, bereinigt dieser Prozess anschließend die CLR und fährt sie herunter – die App-Domäne wird also entladen und die Runtime beendet.

Ebene 4: Verwalteter Einstiegspunkt: Diese Ebene ist die erste Ebene, die in verwaltetem Code erstellt wurde. Sie ist für Folgendes zuständig:

  1. Laden von Assemblys und Berücksichtigen der Abhängigkeiten aus dem Bibliotheksordner.
  2. Einrichten von „IApplicationEnvironment“ und der Infrastruktur für die Einschleusung von Codeabhängigkeiten.
  3. Aufrufen des Haupteinstiegspunkts der angegebenen Anwendung oder des angegebenen Anwendungshosts.

In dieser Ebene werden Assemblys nur aus dem Basispfad der Anwendung oder den angegebenen Bibliotheksordnern geladen. Die nächste Ebene fügt weitere Lader zum Auflösen von Abhängigkeiten von NuGet-Paketen oder sogar von zur Laufzeit kompiliertem Code hinzu.

Ebene 5: Anwendungshost/Anwendung: Wenn ein Entwickler eine gesamte Anwendung in Assemblies im Bibliotheksordner auf dem Datenträger kompiliert, ist diese Ebene die Anwendung – die Anwendung, die der Endbenutzer sieht. Wenn Sie so vorgehen möchten, können Sie Ihre Anwendung kompilierten und beim Starten von Ebene 1 den Namen der DLL übergeben, die einen standardmäßigen Haupteinstiegspunkt enthält.

In den meisten Szenarien werden Sie jedoch einen Anwendungshost verwenden, um die Auflösung von Anwendungsabhängigkeiten zu unterstützen und Ihre App auszuführen. „Microsoft.Framework.ApplicationHost“ ist der in der KRE bereitgestellte Anwendungshost, und seine Zuständigkeiten sind unter anderem:

  1. Durchlaufen der Abhängigkeiten in „project.json“ und Auflösen der Abhängigkeiten, die von der App verwendet werden. Die Logik zum Durchlaufen der Abhängigkeiten wird ausführlicher unter bit.ly/1y5lZEm beschrieben.
  2. Hinzufügen weiterer Assemblylader, die Assemblys aus verschiedenen Quellen laden können, etwa aus installierten NuGet-Paketen, mittels Roslyn zur Laufzeit kompiliertem Quellcode usw.
  3. Aufrufen des Einstiegspunkts der Assembly, deren Name beim Starten des systemeigenen Prozesses als Argument übergeben wurde. Die Assembly kann alles mit einem Einstiegspunkt sein, sofern der ApplicationHost weiß, wie es geladen wird. Der ApplicationHost der KRE versteht sich auf das Auffinden einer Main-Methode vom Typ „public void“. Dies ist der Einstiegspunkt, der zum Einrichten der Hostingebene von ASP.NET verwendet wird, der „Startup.cs“ finden und die Methode „Configure“ Ihrer Webanwendung ausführen kann.

Eins gilt es zu beachten, während Sie die KRE kennenlernen, nämlich, dass diese Aktivitäten auf einer unteren Ebene des Stacks erfolgen. Beim Betrieb auf der KRE-Ebene dreht sich die Welt noch sehr stark um das Finden und Laden von DLLs. Die Logik, die Ihnen erlaubt, auf Anwendungsebene nur noch an Pakete und andere Abhängigkeiten der obersten Ebene zu denken, ist eben gerade in der KRE enthalten.

Plattformübergreifende SDK-Tools

Die KRE bildet zusammen mit einem SDK, das alles Nötige zum Erstellen von plattformübergreifenden .NET-Anwendungen enthält, ein Paket. In meinem früheren Artikel zu ASP.NET 5 habe ich beschrieben, wie das KVM-Tool (KRE Version Manager) zum Auflisten der auf dem Computer installierten KREs, zum Installieren von neuen KREs und zum Auswählen der gewünschten KRE verwendet werden kann. Anweisungen zum Installieren des KVMs für Ihr Betriebssystem finden Sie unter bit.ly/1y5mqyi.

Der KVM installiert KREs aus einem NuGet-Feed, der mithilfe der Umgebungsvariablen „KRE_FEED“ konfiguriert wird. Die KREs stellen keine NuGet-Pakete im herkömmlichen Sinn dar, sie sind keine Pakete, zu denen zu irgend einem Zeitpunkt Abhängigkeiten bestehen. NuGet ist aber ein komfortables Verfahren zum Verteilen und Versionieren der KREs. Standardmäßig wird eine KRE durch Kopieren und Extrahieren der ZIP-Datei der KRE nach „%USERPROFILE%\.k\runtimes“ installiert.

Ich hatte außerdem bereits das KPM-Tool (K-Paket-Manager) zum Installieren, Wiederherstellen und Erstellen von NuGet-Paketen vorgestellt. Aktuell besteht der Plan, das KPM-Tool in „nuget“ umzubenennen und es am vorhandenen NuGet-Client auszurichten. Im Rahmen dieser Ausrichtung wurden einige Unterbefehle von KPM bereits umbenannt. Sie verwenden jetzt den Befehl „bundle“, um eine Anwendung für die Veröffentlichung zu bündeln, und den Befehl „pack“, um NuGet-Pakete für ein Projekt zu erstellen. Der aktualisierte Befehl „build“ produziert eine rohe Buildausgabe ohne Paketierung irgendwelcher Art. Es gibt außerdem einen neuen Befehl „wrap“, der den Tools das Verweisen auf ein vorhandenes csproj-basiertes Projekt in „project.json“ ermöglicht. Standardmäßig werden Pakete jetzt im Ordner „%USERPROFILE%\.k\packages“ installiert, Sie können dies aber steuern, indem Sie in Ihrer global.json-Datei einen Pfad für Pakete festlegen.

Eine plattformübergreifende .NET-Konsolenanwendung

Jetzt wollen wir uns ansehen, wie eine einfache plattformübergreifende .NET-Konsolenanwendung mithilfe der KRE erstellt wird. Zunächst muss eine DLL mit dem Einstiegspunkt erstellt werden. Das kann mithilfe der Projektvorlage „ASP.NET 5-Konsolenanwendung“ in Visual Studio 2015 erfolgen. Der Code sollte etwa so aussehen:

public class Program
{
  public void Main(string[] args)
  {
    Console.WriteLine("Hello World");
    Console.ReadLine();
  }
}

Das sieht recht vertraut aus, beachten Sie aber, dass der Einstiegspunkt tatsächlich eine Instanzmethode darstellt. Über den statischen Einstiegspunkt „Program.Main“ hinaus unterstützt die KRE instanzbasierte Einstiegspunkte. Der Einstiegspunkt in Main kann sogar asynchron ausgeführt werden und einen Task zurückgeben. Dadurch, dass der Einstiegspunkt in Main eine Instanzmethode darstellt, können Dienste von der Laufzeitumgebung in die Anwendung eingeführt werden.

Sie können diese Anwendung aus Visual Studio oder auf der Befehlszeile ausführen, indem Sie in dem Verzeichnis, das die project.json-Datei der Anwendung enthält, „k run“ ausführen. Der K-Befehl ist nur eine einfache Batchdatei, und der Befehl „k run“ kann in folgender Weise erweitert werden:

klr.exe --appbase . Microsoft.Framework.ApplicationHost run

Der K-Befehl führt den systemeigenen Prozess („klr.exe“) aus, legt die Anwendungsbasis als aktuelles Verzeichnis fest, und gibt dann den standardmäßigen Anwendungshost an. Der systemeigene Prozess hat keine Kenntnis vom standardmäßigen Anwendungshost – er sucht nur nach einem Standardeinstiegspunkt in der Microsoft.Framework.ApplicationHost-Assembly und ruft ihn auf.

Wenn Sie nicht den standardmäßigen Anwendungshost verwenden möchten, können Sie die systemeigene Ebene aufrufen, um Ihre Anwendung direkt aufzurufen. Erstellen Sie dazu zuerst das Projekt, um die DLL für die Konsolenanwendung zu erzeugen. Achten Sie darauf, die Option „Ausgaben beim Buildvorgang generieren“ in den Buildeigenschaften des Projekts zu aktivieren. Sie finden die Buildausgabe im Artefaktenverzeichnis in Ihrem Projektmappenordner. Navigieren Sie zu dem Verzeichnis, das die integrierte DLL für die verwendete KRE-Plattform enthält, und rufen Sie „klr.exe <DLL-Name>“ auf, um die Ausgabe der Konsolenanwendung anzuzeigen.

Das direkte Aufrufen einer DLL ist ein sehr geradliniger und auf sehr tiefer Ebene ablaufender Ansatz zum Erstellen von Anwendungen. Sie verwenden nicht den standardmäßigen Anwendungshost, daher verzichten Sie auf project.json-Unterstützung und die Unterstützung der verbesserten, auf NuGet basierenden Abhängigkeitsverwaltung. Stattdessen werden alle benötigten Bibliotheken einfach aus den angegebenen Bibliotheksordnern geladen. Für den Rest des Artikels verwenden wir den standardmäßigen Anwendungshost.

Sie können den IServiceManifest-Dienst verwenden, um alle Dienste aufzuzählen, die über die Laufzeitumgebung verfügbar sind. Sie müssen überhaupt keine weiteren Abhängigkeiten angeben, um diese Assembly zu verwenden, da es sich um eine ANI (Assembly Neutral Interface) handelt. ANIs sind Typen, die ausschließlich anhand ihres Namens und Namespaces identifiziert werden und stellen ein Feature der KRE dar. Zwei assemblyneutrale Typen in verschiedenen Assemblys, jedoch mit gleichem Namen und gleichen Namespaces, werden als gleichbedeutend angesehen. Das bedeutet, dass allgemeine Abstraktionen einfach lokal deklariert werden können, statt eine Abhängigkeit von einer gemeinsamen Komponente deklarieren zu müssen.

Sie können in der folgenden Weise eine eigene assemblyneutrale IServiceManifest-Schnittstelle in Ihrer Konsolenanwendung definieren:

namespace Microsoft.Framework.DependencyInjection.ServiceLookup
{
  [AssemblyNeutral]
  public interface IServiceManifest
  {
    IEnumerable<Type> Services { get; }
  }
}

Aber woher kommt dieses „AssemblyNeutralAttribute“? Naja, es ist natürlich assemblyneutral! Also deklarieren Sie es ebenfalls lokal:

namespace Microsoft.Net.Runtime
{
  [AssemblyNeutral]
  [AttributeUsage(AttributeTargets.All, 
    Inherited = false, AllowMultiple = true)]
  public sealed class AssemblyNeutralAttribute : Attribute
  {
  }
}

Diese assemblyneutralen Typen werden zur Laufzeit mit den gleichen von der KRE verwendeten Typen vereinigt.

Der Code in Abbildung 1 fügt den IServiceManifest-Dienst in die Anwendung ein, indem er der Klasse „Program“ einen Konstruktor hinzufügt und dann durch die Dienste iteriert.

Abbildung 1 Einfügen des IServiceManifest-Diensts in die Anwendung

namespace ConsoleApp1
{
  public class Program
  {
    public Program(IServiceManifest serviceManifest)
    {
      ServiceManifest = serviceManifest;
    }
    IServiceManifest ServiceManifest { get; }
    public void Main(string[] args)
    {
      foreach (Type type in ServiceManifest.Services)
      {
        Console.WriteLine(type.FullName);
      }
    }
  }
}

Der Anwendungshost bietet Unterstützung für „project.json“, fügt zusätzliche Assemblyresolver hinzu (für den Umgang mit NuGet-Paketen, Projektverweisen usw.) und macht eine Reihe weiterer Dienste für die Anwendung verfügbar, bevor er den Einstiegspunkt für die Anwendung aufruft. Wenn Sie die Konsolenanwendung ausführen, sollte die Ausgabe jetzt so aussehen:

Microsoft.Framework.Runtime.IAssemblyLoaderContainer
Microsoft.Framework.Runtime.IAssemblyLoadContextAccessor
Microsoft.Framework.Runtime.IApplicationEnvironment
Microsoft.Framework.Runtime.IFileMonitor
Microsoft.Framework.Runtime.IFileWatcher
Microsoft.Framework.Runtime.ILibraryManager
Microsoft.Framework.Runtime.ICompilerOptionsProvider
Microsoft.Framework.Runtime.IApplicationShutdown

Es gibt Dienste für die Überwachung von Dateien, das Durchqueren der Struktur der „Bibliotheken“ (Projekte, Pakete, Assemblys) in der Anwendung, das Abrufen der verwendeten Kompilierungsoptionen und das Herunterfahren der Anwendung. Da ASP.NET 5 und die KRE sich noch im Vorschaustadium und in der aktiven Entwicklungsphase befinden, kann die angezeigte Liste der Dienste im Einzelnen abweichen.

Hosting

Die Hostingebene von ASP.NET wird auf der KRE aufbauend ausgeführt und ist dafür zuständig, den Webserver für die Ausführung und die Startlogik für die Anwendung zu finden, die Anwendung auf dem Server zu hosten und dann aufzuräumen, wenn die Anwendung heruntergefahren wird. Sie stellt Anwendungen darüber hinaus eine Reihe weiterer Dienste im Hostingzusammenhang zur Verfügung.

Die Hosting-DLL von ASP.NET („Microsoft.AspNet.Hosting“, unter bit.ly/­1uB6ulW) weist eine Einstiegspunktmethode auf, die vom KRE-Anwendungshost aufgerufen wird. Sie können den zu verwendenden Webserver konfigurieren, indem Sie die Befehlszeilenoption „--server“ oder andere Quellen von Konfigurationsdaten, wie etwa „config.json“ oder Umgebungsvariablen, angeben. Anschließend lädt die Hostingebene die angegebene Serverassembly / den angegebenen Typ, um eine „IServerFactory“ zu finden, die zum Initialisieren und Starten des Servers verwendet werden kann. Normalerweise muss der Server in „project.json“ unter den Abhängigkeiten aufgelistet sein, damit er geladen werden kann.

Beachten Sie, dass in „project.json“ definierte Befehle lediglich Mengen von ergänzenden Befehlszeilenargumenten für „klr.exe“ darstellen. Beispielsweise enthält die standardmäßige Projektvorlage „ASP.NET-Webanwendung“ eine Reihe von Befehlen in „project.json“, die etwa so aussehen:

"commands": {
  /* Change the port number when you are self hosting this application */
  "web": "Microsoft.AspNet.Hosting 
    --server Microsoft.AspNet.Server.WebListener
    --server.urls http://localhost:5000",
  "gen": "Microsoft.Framework.CodeGeneration",
  "ef":  "EntityFramework.Commands"
},

Wenn Sie „k web“ ausführen, wird tatsächlich Folgendes ausgeführt:

klr.exe --appbase . Microsoft.Framework.ApplicationHost Microsoft.AspNet.Hosting
  --server Microsoft.AspNet.Server.WebListener --server.urls http://localhost:5000

Start

Die Hostingebene von ASP.NET ist ebenfalls dafür zuständig, die Startlogik für Ihre Anwendung zu finden. Normalerweise ist die Startlogik der Anwendung in einer Klasse „Startup“ definiert, die eine Methode „Configure“ zum Einrichten Ihrer Anforderungspipeline und eine Methode „ConfigureServices“ zum Konfigurieren aller von der Anwendung benötigten Dienste enthält, wie in Abbildung 2 dargestellt.

Abbildung 2 Die Startup-Klasse

namespace WebApplication1
{
  public class Startup
  {
    public void ConfigureService(IServiceCollection services)
    {
      // Add services for your application here
    }
    public void Configure(IApplicationBuilder app)
    {
      // Configure your application pipeline here
    }
  }
}

In Ihrer Configure-Methode verwenden Sie die IApplicationBuilder-Schnittstelle, um die Anforderungspipeline für Ihre Anwendung zu erstellen. Im Application Builder können Sie mit „Use“ Middleware verwenden, mit „New“ Anwendungsgeneratoren erstellen und mit „Build“ einen Anforderungsstellvertreter erzeugen. Der Anforderungsstellvertreter ist das Konstrukt der Kernlaufzeitumgebung in ASP.NET 5. Ein Anforderungsstellvertreter nimmt einen HttpContext an und führt asynchron etwas Nützliches mit ihm aus:

public delegate Task RequestDelegate(HttpContext context);

ASP.NET 5-Middleware nimmt als Eingabe den nächsten Anforderungsstellvertreter in der Pipeline an und stellt einen Anforderungsstellvertreter mit der Middlewarelogik zur Verfügung. Der zurückgegebene Anforderungsstellvertreter ruft den nächsten Anforderungsstellvertreter in der Pipeline auf oder auch nicht. Als Abkürzung für das Ausführen von Middlewarelogik, die nicht den nächsten Anforderungsstellvertreter aufruft, kann die Erweiterungsmethode „Run“ von „IApplicationBuilder“ verwendet werden:

app.Run(async context => await context.Response.WriteAsync("Hello, world!"));

Das bewirkt das Gleiche wie die folgende Inline-Middleware, bei der einfach der nächste Parameter ignoriert wird:

app.Use(next => async context =>
  await context.Response.WriteAsync("Hello, world!"));

Um wiederverwendbare Middleware zu erstellen, können Sie sie als Klasse erstellen, in der gemäß Konvention der nächste Anforderungsstellvertreter zusammen mit allen von der Middleware benötigten zusätzlichen Diensten oder Parametern in den Konstruktor eingefügt wird. Anschließend implementieren Sie den Anforderungsstellvertreter für die Middleware als asynchrone Methode „Invoke“, wie in Abbildung 3 dargestellt.

Abbildung 3 Implementieren von Middleware als wiederverwendbare Klasse

using Microsoft.AspNet.Builder;
using Microsoft.AspNet.Http;
using System.Threading.Tasks;
public class XHttpHeaderOverrideMiddleware
{
  private readonly RequestDelegate _next;
  public XHttpHeaderOverrideMiddleware(RequestDelegate next)
  {
    _next = next;
  }
  public Task Invoke(HttpContext httpContext)
  {
    var headerValue =
      httpContext.Request.Headers["X-HTTP-Method-Override"];
    var queryValue =
      httpContext.Request.Query["X-HTTP-Method-Override"];
    if (!string.IsNullOrEmpty(headerValue))
    {
      httpContext.Request.Method = headerValue;
    }
    else if (!string.IsNullOrEmpty(queryValue))
    {
      httpContext.Request.Method = queryValue;
    }
    return _next.Invoke(httpContext);
  }
}

Sie können Middleware gemäß dieser Konvention mithilfe der Erweiterungsmethode „UseMiddleware<T>“ von „IApplicationBuilder“ verwenden. Alle weiteren Optionen, die Sie an diese Methode übergeben, werden in den Middleware-Konstruktor hinter dem nächsten Anforderungsstellvertreter und allen eingefügten Diensten eingefügt. Gemäß Konvention sollte Middleware auch eine eigene, spezifische Erweiterungsmethode „Use“ für „IApplicationBuilder“ definieren, etwa so:

public static class BuilderExtensions
{
  public static IApplicationBuilder UseXHttpHeaderOverride(
    this IApplicationBuilder builder)
  {
    return builder.UseMiddleware<XHttpHeaderOverrideMiddleware>();
  }
}

Anschließend fügen Sie die Middleware der Anwendungspipeline hinzu, in dieser Weise:

public class Startup
{
  public void Configure(IApplicationBuilder app)
  {
    app.UseXHttpHeaderOverride();
  }
}

ASP.NET 5 wird mit einer umfangreichen Sammlung bereits generierter Middleware geliefert. Es gibt Middleware für das Verarbeiten statischer Dateien, Routing, Fehlerbehandlung, Diagnose und Sicherheit. Sie finden Middleware von Microsoft und der Community in Form von NuGet-Paketen auf nuget.org, die Sie herunterladen und installieren können.

Konfigurieren von Diensten

Wie bei allen anderen Einstiegspunkten, die Sie in diesem Stack gesehen haben, können auch in die Klasse „Startup“ Dienste von der Hostingebene eingefügt werden, entweder durch Einfügen in den Konstruktor oder als zusätzliche Parameter der Methoden „Configure“ und „ConfigureServices“. Wenn Sie die beim Starten verfügbaren Dienste aufzählen (mithilfe von „IServiceManifest“), stellen Sie fest, dass sogar noch mehr Dienste verfügbar sind:

Microsoft.Framework.Runtime.IAssemblyLoaderContainer
Microsoft.Framework.Runtime.IAssemblyLoadContextAccessor
Microsoft.Framework.Runtime.IApplicationEnvironment
Microsoft.Framework.Runtime.IFileMonitor
Microsoft.Framework.Runtime.IFileWatcher
Microsoft.Framework.Runtime.ILibraryManager
Microsoft.Framework.Runtime.ICompilerOptionsProvider
Microsoft.Framework.Runtime.IApplicationShutdown
Microsoft.AspNet.Hosting.IHostingEnvironment
Microsoft.Framework.Runtime.IProjectResolver
Microsoft.Framework.Logging.ILoggerFactory
Microsoft.Framework.Runtime.ICache
Microsoft.Framework.Runtime.ICacheContextAccessor
Microsoft.Framework.DependencyInjection.ITypeActivator
Microsoft.Framework.Runtime.IAssemblyLoadContextFactory

Die IHostingEnvironment-Dienste geben Ihnen Zugriff auf den Webstammpfad für Ihre Anwendung (normalerweise Ihr Ordner „www“) und ebenso auf eine IFileProvider-Abstraktion für Ihren Webstamm. Der IProject­Resolver kann verwendet werden, um andere Projekte in Ihrer Projektmappe zu finden. Protokollierungs- und Cachedienste sind verfügbar, ebenso ein Typaktivator, der auf die Einfügung von Abhängigkeiten reagiert. Mit „IAssemblyLoadContextFactory“ erhalten Sie eine Abstraktion für das Erstellen neuer Assemblyladekontexte.

Mithilfe der Methode „ConfigureServices“ der Klasse „Startup“ können Sie vorhandene Dienste konfigurieren und Ihrer Anwendung neue hinzufügen. Die Methode „ConfigureServices“ nimmt eine „IServiceCollection“ als Argument an, der Sie weitere Dienste hinzufügen oder deren vorhandene Sie ändern können. ASP.NET 5 enthält einen einfachen integrierten IoC-Container, der in der Managed Entry Point-Ebene eingerichtet ist, um das Bootstrapping des Systems zu ermöglichen, Sie können den integrierten Container aber leicht durch einen Container Ihrer Wahl ersetzen.

Frameworks stellen normalerweise eine Erweiterungsmethode „Add“ zum Hinzufügen ihrer Dienste zur „IServiceCollection“ zur Verfügung. Hier sehen Sie ein Beispiel, wie Sie die von ASP.NET MVC 6 verwendeten Dienste hinzufügen:

public void ConfigureServices(IServiceCollection services)
{
  // Add MVC services to the services container
  services.AddMvc();
}

Dienste, die der Dienstsammlung hinzugefügt werden, können einem von drei Typen angehören: kurzlebig, bereichsbezogen oder singleton. Kurzlebige Dienste werden bei jeder Anforderung aus dem Container neu erstellt. Bereichsbezogene Dienste werden nur erstellt, wenn sie im aktuellen Bereich noch nicht vorhanden sind. Für Webanwendungen wird für jede Anforderung ein Containerbereich erstellt, daher können Sie bereichsbezogene Dienste als pro Anforderung auffassen. Singleton-Dienste werden immer nur einmal erstellt.

Die app.config-Dateien im Stil von „Web.config“ und „System.Configuration“ werden in ASP.NET 5 nicht unterstützt. Stattdessen wird mit ASP.NET 5 eine neue, vereinfachte Konfigurations-API („Microsoft.Framework.ConfigurationModel“, unter bit.ly/1yxC7gA) für das Arbeiten mit Konfigurationsdaten eingeführt. Mithilfe der neuen Konfigurations-API können Sie Konfigurationsdaten aus einer Vielzahl von Quellen abrufen und ändern. Standardmäßige Konfigurationsanbieter sind für JSON, XML, INI, Befehlszeilenparameter und Umgebungsvariablen enthalten. Sie können mehrere Anbieter hinzufügen, diese werden dann in der Reihenfolge des Hinzufügens verwendet. Durch Unterstützung einer auf der Umgebung basierten Konfiguration können Sie eine Anwendung einfacher in einer Umgebung bereitstellen und sie die passenden Einstellungen für die jeweilige Umgebung selbst auffinden lassen.

Um die Konfigurationsdaten zu initialisieren, fügen Sie die gewünschten Konfigurationsanbieter zu einer neuen Instanz von „Configuration“ hinzu:

public Startup(IHostingEnvironment env)
{
  Configuration = new Configuration()
    .AddJsonFile("config.json")
    .AddEnvironmentVariables();
}

Anschließend können Sie Konfigurationswerte anhand des Namens anfordern:

string user = Configuration.Get("user");
string password = Configuration.Get("password");

Sie können Konfigurationsdaten mithilfe des Optionsmodells in der gesamten Anwendung in stark typisierter Weise verfügbar machen. Optionen sind einfach POCO-Klassen (Plain Old C# Object), die über eine Reihe von Eigenschaften für die Konfiguration verfügen. Sie können Optionen in Ihrer Anwendung verfügbar machen, indem Sie „AddOptions“ aufrufen, wodurch dem Container der Dienst „IOptions<TOption>“ hinzugefügt wird. Dieser Dienst kann immer dann, wenn die Einfügung von Abhängigkeiten unterstützt wird, für den Zugriff auf Optionen verschiedener Typen verwendet werden.

Zum Konfigurieren von Optionen fügen Sie dem Container die IConfigureOptions<TOptions>-Dienste hinzu. Die Standardimplementierung des IOptions<TOption>-Diensts sammelt alle IConfigure­Options<TOption>-Dienste und verwendet sie, um die Instanz der Optionen zu konfigurieren, bevor sie Endbenutzern zur Verfügung gestellt wird. Als abgekürztes Verfahren für das Hinzufügen von Konfigurationslogik für Optionen können Sie die Methode „Configure<TOption>“ verwenden, in dieser Weise:

services.Configure<MvcOptions>(options => options.Filters.Add(
  new MyGlobalFilter()));

Das Optionsmodell und das Konfigurationsmodell arbeiten nahtlos zusammen. Sie können Konfigurationsdaten auf einfache Weise an Optionen binden, durch Zuordnung von Eigenschaftsnamen mithilfe von Configure-Überladungen, die eine Configuration-Instanz annehmen, wie hier:

services.Configure<MyOptions>(Configuration);

Anforderungsverarbeitung

Sobald der Webserver gestartet ist, beginnt er, auf Anforderungen zu lauschen und bei jeder Anforderung die Anwendungspipeline aufzurufen. Der Server fasst jede Anforderung als ein Serverumgebungsobjekt auf, das eine Sammlung von fein aufgelösten Funktionsschnittstellen verfügbar macht. Es gibt Funktionsschnittstellen für das Senden von Dateien, Websockets, Sitzungsunterstützung, Clientzertifikate usw. Die vollständige Liste der unterstützten Funktionsschnittstellen finden Sie auf GitHub unter bit.ly/1Dh7eBC. Beispielsweise sehen Sie hier die Funktionsschnittstelle für die HTTP-Anforderung:

[AssemblyNeutral]
public interface IHttpRequestFeature
{
  string Protocol { get; set; }
  string Scheme { get; set; }
  string Method { get; set; }
  string PathBase { get; set; }
  string Path { get; set; }
  string QueryString { get; set; }
  IDictionary<string, string[]> Headers { get; set; }
  Stream Body { get; set; }
}

Normalerweise werden die verschiedenen Funktionsschnittstellen nicht direkt mit Code angesprochen. Webserver verwenden Funktionsschnittstellen, um Funktionen der unteren Ebenen für die Hostebene verfügbar zu machen. Die Hostingebene umschließt diese Funktionsschnittstellen mit einem stark typisierten HttpContext, der an die Anwendung übergeben wurde. Dieses Maß der lockeren Kopplung zwischen dem Webserver, der Hostingebene und den Anwendungen ermöglicht das Implementieren und erneute Verwenden von Servern, ohne sich an ein bestimmtes Hostingmodell zu binden und ermöglicht das Hosting von Anwendungen auf verschiedenen Servern. In ASP.NET 5 ist Unterstützung für die Ausführung auf IIS („Microsoft.AspNet.Server.IIS“) integriert, direkt auf einem dünnen HTTP.SYS-Wrapper („Microsoft.AspNet.Server.Web­Listener“) und auf einem neuen plattformübergreifenden .NET-Webserver namens Kestrel („Microsoft.AspNet.Server.Kestrel“).

Der Communitystandard OWIN (Open Web Interface for .NET, owin.org) verfolgt ähnliche Ziele einer losen Kopplung für Webanwendungen. OWIN standardisiert die Kommunikation von .NET-Server und Anwendungen untereinander. ASP.NET 5 unterstützt OWIN über das Microsoft.AspNet.Owin-Paket (bit.ly/15IQwA5). Sie können ASP.NET 5 auf einem OWIN-basierten Webserver (bit.ly/1DaU4FZ) und OWIN-Middleware innerhalb der ASP.NET 5-Pipeline (bit.ly/1EqmoIB) verwenden. 

Das Katana-Projekt (katanaproject.codeplex.com) war der erste Versuch von Microsoft, Support für OWIN zu implementieren, und viele der in ASP.NET 5 verwirklichten Ideen und Konzepte wurden aus ihm abgeleitet. Katana wies ein ähnliches Modell für den Aufbau einer Middlewarepipeline und Hosting auf mehreren Servern auf. Im Gegensatz zu Katana, das die OWIN-Abstraktionen der niederen Ebenen direkt für den Entwickler verfügbar machte, stellt ASP.NET 5 eine Brücke von OWIN zu komfortableren und benutzerfreundlicheren Abstraktionen bereit. Mit etwas zusätzlichem Code können Sie Katana-Middleware mithilfe der OWIN-Brücke immer noch in ASP.NET 5 verwenden (ein Beispiel dafür finden Sie unter bit.ly/1BpaXe2).

Zusammenfassung

Die ASP.NET 5 Runtime wurde von Grund auf für die Unterstützung von plattformübergreifenden Webanwendungen konzipiert. ASP.NET 5 verfügt über eine flexible Struktur mit mehreren Ebenen, die unter .NET Framework, .NET Core oder sogar plattformübergreifend unter Mono (und zukünftig auch dort unter .NET Core!) ausgeführt werden kann. Das neue Hostingmodell von ASP.NET 5 macht es einfach, Anwendungen zusammenzustellen und sie auf dem Webserver Ihrer Wahl bereitzustellen. Die gesamte Runtime ist dafür ausgelegt, die Einfügung von Abhängigkeiten zu unterstützen und sich leicht konfigurieren zu lassen. Ich hoffe, Ihnen haben die Informationen zur neuen ASP.NET 5 Runtime gefallen! Feedback und Beiträge sind in unserem GitHub-Projekt unter github.com/aspnet/home willkommen.


Daniel Roth ist Senior Program Manager des ASP.NET-Teams, das zurzeit an ASP.NET 5 arbeitet. Die .NET-Entwicklung ist seine Passion, und Kunden schätzen an ihm, dass er Frameworks einfach und leicht zu verwenden gestaltet.

Unser Dank gilt den folgenden technischen Experten von Microsoft für die Durchsicht dieses Artikels: Glenn Condron, David Fowler, Murat Girgin und Chris Ross