Peter Bucher Ralf Westphal

Blog

Nutze den Augenblick
und teile der Welt mit, was Du zu sagen hast.

WCF ohne .svc

Donnerstag, 19. März 2009, 19:21 Uhr
Permalink | Kommentare (2) | Kommentare als RSSRSS

Um in .NET einen WCF-Dienst auszuführen, muss prinzipiell lediglich eine Instanz der Klasse ServiceHost erzeugt werden, die anschließend das Hosting für den jeweiligen Dienst übernimmt.

Nahezu jedes Buch zu WCF enthält dementsprechend ein Beispiel, wie man einen einfachen Host für WCF-Dienste auf Basis der Konsole entwickelt. Auf Grund gewisser Anforderungen wie Verfügbarkeit, Skalierbarkeit und ähnlichem genügt ein solcher Host in der Praxis jedoch in den seltensten Fällen.

Da .NET erst ab der Version 4.0 über einen Application Server verfügen wird, stellt derzeit der IIS die beste Alternative dar, da dieser neben Webseiten auch WCF-Dienste hosten kann. Allerdings benötigt der IIS hierfür in jedem Fall eine .svc-Datei, die auf den eigentlichen Dienst verweist:

<% @ServiceHost language="C#" Debug="true" Service="MyService" CodeBehind="~/App_Code/MyService.cs" %>

Sofern man Wert auf ein einfaches Deployment legt und den WCF-Dienst in einer einzigen DLL bereitstellen will, die sämtlichen Code enthält, stößt man nun auf ein Problem: Es ist scheinbar unter WCF nicht möglich, die .svc-Datei ebenfalls in die DLL auszulagern – es müssen also immer zwei Dateien angefasst werden.

Doch es gibt eine Lösung für dieses Problem: ASP.NET stellt im Namensraum System.Web.Hosting die Klasse VirtualPathProvider zur Verfügung, mit deren Hilfe ein virtuelles Dateisystem implementiert werden kann.

Um also einen WCF-Dienst ohne .svc-Datei im IIS zu hosten, muss ein eigener VirtualPathProvider implementiert werden, der auf Anfragen nach der Datei Service.svc mit einer per Code erzeugten Variante reagiert.

Damit ein VirtualPathProvider funktioniert, müssen die beiden Methoden FileExists und GetFile überschrieben werden, die zudem prüfen müssen, ob der aktuelle Provider überhaupt zuständig ist – falls nicht, wird die Ausführung an den nächsten in ASP.NET registrierten Provider übergeben:

using System.Web.Hosting;
using System.Web;

namespace silkveil.net.Wcf
{
    /// <summary>
    /// Provides activation for a WCF service without the need for a .svc file.
    /// </summary>
    public class ServiceActivationProvider : VirtualPathProvider
    {
        /// <summary>
        /// Checks whether the specified path is a virtual one.
        /// </summary>
        /// <param name="virtualPath">The virtual path.</param>
        /// <returns><c>true</c> if the path is virtual; otherwise <c>false</c>.</returns>
        private static bool IsVirtualPath(string virtualPath)
        {
            // Check whether the path is virtual.
            virtualPath = VirtualPathUtility.ToAppRelative(virtualPath);
            return virtualPath == "~/Service.svc";
        }

        /// <summary>
        /// Gets a value that indicates whether a file exists in the virtual file system.
        /// </summary>
        /// <returns>
        /// <c>true</c> if the file exists in the virtual file system; otherwise, <c>false</c>.
        /// </returns>
        /// <param name="virtualPath">The path to the virtual file.</param>
        public override bool FileExists(string virtualPath)
        {
            // If the path is not virtual, delegate to the previous provider in the provider chain.
            if(!IsVirtualPath(virtualPath))
            {
                return this.Previous.FileExists(virtualPath);
            }

            // Otherwise, always return true.
            return true;
        }

        /// <summary>
        /// Gets a virtual file from the virtual file system.
        /// </summary>
        /// <returns>
        /// A descendent of the <see cref="T:System.Web.Hosting.VirtualFile" /> class that
        /// represents a file in the virtual file system.                
        /// </returns>
        /// <param name="virtualPath">The path to the virtual file.</param>
        public override VirtualFile GetFile(string virtualPath)
        {
            // If the file does not exist in this virtual path provider, delegate execution to
            // the previous provider in the provider chain.
            if (!IsVirtualPath(virtualPath))
            {
                return this.Previous.GetFile(virtualPath);
            }

            // Create a Service.svc file on the fly and return it to the caller.
            return new ServiceActivationFile(virtualPath);
        }
    }
}

Außer diesem Provider muss zusätzlich auch noch eine Klasse von VirtualFile abgeleitet werden, welche die eigentliche .svc-Datei erzeugt:

using System.IO;
using System.Web.Hosting;

namespace silkveil.net.Wcf
{
    /// <summary>
    /// Represents a virtual .svc file.
    /// </summary>
    public class ServiceActivationFile : VirtualFile
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="ServiceActivationFile" /> type.
        /// </summary>
        /// <param name="virtualPath">The virtual path.</param>
        public ServiceActivationFile(string virtualPath) : base(virtualPath)
        {
        }

        /// <summary>
        /// Returns a read-only stream to the virtual resource.
        /// </summary>
        /// <returns>A read-only stream to the virtual file.</returns>
        public override Stream Open()
        {
            // Create a stream.
            Stream stream = new MemoryStream();
            StreamWriter writer = new StreamWriter(stream);

            // Write to the stream.
            writer.Write("<%@ ServiceHost Language=\"C#\" Debug=\"false\" Service=\"silkveil.net.Wcf.Service\" %>");
            writer.Flush();

            // Reset the stream's pointer to its beginning and return the stream to the caller.
            stream.Seek(0, SeekOrigin.Begin);
            return stream;
        }
    }
}

Kompiliert man diese beiden Klassen mit in die DLL, die den WCF-Dienst erhält, genügt es für ein Deployment zukünftig, nur noch diese DLL zu verteilen. Auf diese Art ist es ein Leichtes, beispielsweise eine bestehende ASP.NET-Anwendung um einen WCF-Dienst zu erweitern – ohne mit einer .svc-Datei hantieren zu müssen.

Allerdings muss der VirtualPathProvider noch registriert werden, damit ASP.NET von seiner Existenz weiß: Dazu genügt es, diesen beim Start der Anwendung in der Datei Global.asax zu registrieren:

// Initialize the service activation provider.
HostingEnvironment.RegisterVirtualPathProvider(new ServiceActivationProvider());

Der entscheidende Punkt an dieser Vorgehensweise ist, dass Dateien, die von einem VirtualPathProvider in ASP.NET geladen werden, nicht – wie beispielsweise bei einem HttpHandler – direkt in den Ausgabestrom geschrieben werden, sondern dass sie zunächst noch von der ASP.NET-Engine verarbeitet werden.

Damit wäre es also auch möglich, an Stelle einer .svc-Datei eine beliebige andere Datei per Code bereitzustellen – .Webseiten, Usercontrols, … einzig Dateien, welche die gesamte Anwendung betreffen, bleiben außen vor, wie beispielsweise die Global.asax und die web.config.

Für alles andere gilt: Der Fantasie sind dabei keine Grenzen gesetzt …

Erste Schritte auf Windows Azure

Sonntag, 8. Februar 2009, 13:42 Uhr
Permalink | Kommentare (0) | Kommentare als RSSRSS

Nachdem ich am 27. Oktober 2008 über Windows Azure berichtet habe, habe ich heute meine ersten Schritte auf diesem Betriebssystem gewagt. Da Cloud Computing zukünftig eine ausgesprochen wichtige Rolle für diverse Aufgaben spielen wird, habe ich mir vorgenommen, meine bestehenden Webseiten nach und nach auf Windows Azure zu migrieren.

Den Anfang habe ich heute mit meiner persönlichen Webseite goloroden.de unternommen, die bereits auf ASP.NET 3.5 basiert, keine Datenbank erfordert und nur wenig dynamische Inhalte verwendet. Das Ergebnis findet sich unter cloud.goloroden.de - abgesehen von der Domain ist die Seite nicht vom Original zu unterscheiden.

Die Migration war ausgesprochen einfach durchzuführen, insgesamt habe ich ungefähr zwei Stunden benötigt. Allerdings habe ich einen Großteil dieser Zeit damit verbracht, auf das Deployment von Windows Azure zu warten: Zwischen dem Hochladen eines Packages und der Verfügbarkeit im Web können durchaus 15 bis 30 Minuten vergehen, in denen man lediglich abwarten kann.

Prinzipiell habe ich folgende Schritte durchgeführt:

  • Neues Cloud-Projekt anlegen: Dieser Vorgang ist in Visual Studio mit wenigen Klicks erledigt. Ein Cloud-Projekt besteht dabei aus einer Solution mit mindestens zwei Projekten. Das eine dieser beiden Projekte enthält die notwendigen Konfigurationsdateien für das Deployment auf Windows Azure, das andere stellt - zumindest im Falle einer Webrole - ein klassisches Web Application Project (WAP) dar.
  • Webseite zu WAP migrieren: Da die bestehende Webseite nicht als Web Application Project vorlag, sondern als einfache Webseite, habe ich dem Cloud-Projekt eine Webrole hinzugefügt und dort die bestehenden Dateien wieder eingeklinkt. Da die bestehende Webseite eine unveränderte Web.config verwendet, habe ich diese wie von Visual Studio erzeugt belassen. Wichtig bei diesem Schritt war, darauf zu achten, in allen .aspx-Dateien das Attribut CodeFile durch CodeBehind zu ersetzen, andernfalls kann die Webseite auf Windows Azure nicht ausgeführt werden.
  • Cloud-Project publishen: Auch dieser Vorgang ist in Visual Studio wiederum mit wenigen Klicks erledigt. Als Ergebnis erhält man zwei Dateien: Zum einen das Package, zum anderen eine Konfigurationsdatei. Beide müssen über das Azure Services Developer Portal in die Cloud hochgeladen und anschließend gestartet werden.

Sobald das Package vom Staging- in den Produktionsbereich verschoben wurde, dauert es noch die oben erwähnten 15 bis 30 Minuten, bis die Webseite tatsächlich erreichbar ist. Zunächst gilt diese Erreichbarkeit nur über eine von Microsoft vorgegebene Domain, das Einrichten eigener Domains ist in der CTP-Phase von Windows Azure noch nicht vorgesehen. Die Einrichtung eines CNAMEs für eine bestehende eigene Domain genügt allerdings als Workaround.

Diese ersten Schritte auf Windows Azure waren zwar noch nichts weltbewegendes, sind aber bestens geeignet, um sich mit den verwendeten Werkzeugen, Webseiten und Prozessen vertraut zu machen. Die nächste Herausforderung wird nun sein, neben einer Webseite auch eine Datenbank auf Windows Azure zu migrieren.