Silverlight

Erstellen von Branchenanwendungen für Unternehmen mit Silverlight, Teil 1

Hanu Kommalapati

Themen in diesem Artikel:

  • Die Silverlight-Laufzeitumgebung
  • Asynchrone Silverlight-Programmierung
  • Domänenübergreifende Richtlinien
  • Beispiel einer Unternehmensanwendung
In diesem Artikel werden folgende Technologien verwendet:
Silverlight 2

Codedownload verfügbar in der MSDN-Codegalerie
Code online durchsuchen

Inhalt

Silverlight-Grundlagen: Die CoreCLR
Silverlight-Laufzeit
Das Anwendungsszenario
Pushbenachrichtigungen mit Socketserver
Asynchrone E/A-Schleifen
Modale Dialogfelder in Silverlight
Implementierung von Pushbenachrichtigungen
Domänenübergreifender Zugriff auf TCP-Dienste
Domänenübergreifende Richtlinien mit TCP-Diensten
Zusammenfassung

Als ich vor kurzem in Houston leitende Angestellte eines großen Unternehmens über die geschäftlichen Auswirkungen von Silverlight informierte, stieß ich damit auf ein sehr geringes Interesse. Die tollen Demos, mit denen ich DeepZoom, Picture-In-Picture und HD-Videoqualität vorführte, sowie meine hochwertigen Animationen hätten die Gruppe eigentlich spielend begeistern müssen. Als ich die Zuhörer in einer Umfrage zu ihrem geringen Interesse befragte, fand ich heraus, dass die Grafiken zwar fantastisch waren, aber nur sehr wenig praktische Leitfäden zum Erstellen datenzentrischer Branchenanwendungen in Unternehmensqualität mittels Silverlight verfügbar sind.

Heutzutage erfordern Anwendungen für Unternehmen die sichere Bereitstellung der Brancheninformationen über Netzwerkgrenzen hinweg, wozu häufig das Internet, eine rollenbasierte Benutzeroberfläche und Datenanpassung (Trimming) verwendet werden. Durch das Ausführen von Silverlight auf dem Client und von Microsoft .NET Framework 3.5 auf dem Server werden hervorragende Möglichkeiten zum Erstellen derartiger skalierbarer und sicherer Branchenanwendungen zur Verfügung gestellt. Die einfache Silverlight-Laufzeit, die innerhalb einer Sandbox ausgeführt wird, stellt Frameworkbibliotheken zur Integration in Backoffice-Datendienste bereit. Um mit Silverlight stabile Anwendungen erstellen zu können, müssen Architekten und Entwickler das Silverlight-Programmiermodell und seine Frameworkfeatures im Kontext einer realen Anwendung verstehen.

Mit diesem Artikel verfolge ich in erster Linie das Ziel, auf Grundlage eines bestimmten Branchenszenarios eine Anwendung von Grund auf neu zu erstellen und während dieses Prozesses verschiedene Aspekte der Silverlight-Entwicklung zu veranschaulichen. Bei der Lösung, die ich erörtern werde, handelt es sich um eine Callcenteranwendung, deren logische Architektur in Abbildung 1 gezeigt wird. In dieser Ausgabe werde ich mich auf Bildschirmbenachrichtigungen, das asynchrone Programmiermodell, Silverlight-Dialogfelder und die Implementierung eines domänenübergreifenden TCP-Richtlinienservers konzentrieren. In Teil 2 werde ich auf Anwendungssicherheit, Webdienstintegration, Anwendungspartitionierung und einige weitere Aspekte der Anwendung eingehen.

fig01.gif

Abbildung 1 Logische Architektur eines Silverlight-Callcenters

Silverlight-Grundlagen: Die CoreCLR

Zunächst möchte ich Ihnen die Silverlight-Grundlagen in Erinnerung rufen. Ich werde damit beginnen, das Innere der Silverlight-Laufzeit zu untersuchen, damit Sie besser verstehen können, was mit Silverlight möglich ist. Die CoreCLR ist der von Silverlight verwendete virtuelle Computer. Sie ähnelt der CLR, die .NET Framework 2.0 und höheren Versionen zugrunde liegt, und enthält ähnliche Typlade- und Garbage Collection (GC)-Systeme.

Die CoreCLR hat ein sehr einfaches CAS-Modell (code access security, Codezugriffssicherheit), das einfacher als das der Desktop-CLR ist, da Silverlight Sicherheitsrichtlinien lediglich auf Anwendungsebene durchsetzen muss. Dies liegt daran, dass sich die CoreCLR als plattformunabhängiger Webclient nicht auf das Vorhandensein spezieller Unternehmens- oder Computerrichtlinien verlassen kann und keinem Benutzer erlauben sollte, bestehende Richtlinien zu ändern. Es gibt einige Ausnahmen wie z. B. OpenFileDialog und IsolatedStorage (Speicherkontingentänderung), bei denen Silverlight die ausdrückliche Zustimmung des Benutzers benötigt, um den Standardregelsatz der Sandbox überschreiben zu können. OpenFileDialog dient dem Zugriff auf das Dateisystem, während IsolatedStorage dazu verwendet wird, auf den isolierten Speicher zuzugreifen und das Speicherkontingent zu erhöhen.

Bei Desktopanwendungen lädt jede ausführbare Datei eine einzige Kopie der CLR, und der Betriebssystemprozess enthält nur eine einzige Anwendung. Jede Anwendung hat eine Systemdomäne, eine gemeinsam genutzte Domäne, eine Standarddomäne sowie zahlreiche explizit erstellte Anwendungsdomänen (siehe JIT und Laufzeit: Detaillierte Untersuchung von .NET Framework, um zu sehen, wie die CLR Laufzeitobjekte erstellt). Ein ähnliches Domänenmodell liegt in der CoreCLR vor. Im Fall von Silverlight werden innerhalb desselben Betriebssystemprozesses mehrere Anwendungen ausgeführt, die aus verschiedenen Domänen stammen können.

In Internet Explorer 8.0 wird jede Registerkarte in ihrem eigenen isolierten Prozess ausgeführt, was zur Folge hat, dass alle Silverlight-Anwendungen, die innerhalb derselben Registerkarte gehostet werden, im Kontext derselben CoreCLR-Instanz ausgeführt werden (siehe Abbildung 2). Da jede Anwendung aus einer anderen Ursprungsdomäne stammen kann, wird aus Sicherheitsgründen jede Anwendung in ihre eigene Anwendungsdomäne geladen. Daher gibt es genauso viele CoreCLR-Instanzen wie Registerkarten, in denen gerade Silverlight-Anwendungen gehostet werden.

Ähnlich wie bei der Desktop-CLR erhält jede Anwendungsdomäne ihren eigenen Pool statischer Variabler. Jeder domänenspezifische Pool wird während des Bootstrappingprozesses der betreffenden Anwendungsdomäne initialisiert.

fig02.gif

Abbildung 2 Jede Silverlight-Anwendung wird in ihrer eigenen Anwendungsdomäne ausgeführt

Silverlight-Anwendungen können nicht ihre eigenen benutzerdefinierten Anwendungsdomänen erstellen, denn diese Möglichkeit ist für die interne Verwendung reserviert. Ausführlichere Informationen zur CoreCLR finden Sie in den folgenden Artikeln des CLR-Teams in der Rubrik „Tiefe Einblicke in CLR“: "Programmieren von Silverlight mit der CoreCLR und Sicherheit in Silverlight 2.

Silverlight-Laufzeit

Silverlight wurde für eine breite Palette von Anwendungen mit einem unterschiedlichen Bedarf an Framework- und Bibliotheksunterstützung entwickelt. Eine einfache Anwendung könnte beispielsweise Audiodateien von nur wenigen Bytes abspielen, um auf einer Wörterbuchwebsite bei der Aussprache der Wörter zu helfen, oder eine Banneranzeige einblenden. Andererseits erfordern Branchenanwendungen für Unternehmen Sicherheit, Datenschutz, Statusverwaltung, Integration in andere Anwendungen und Dienste sowie Instrumentationsunterstützung, um nur eine kleine Auswahl zu nennen. Gleichzeitig muss Silverlight eine kleinere Laufzeit besitzen, damit die Bereitstellung über das Internet bei langsameren Verbindungen kein Problem darstellt.

Diese Anforderungen scheinen im Konflikt zueinander zu stehen, aber das Silverlight-Team hat dieses Problem gelöst, indem es das Framework in die geschichtete Ansicht aufgeteilt hat, die in Abbildung 2 gezeigt wird. Die CoreCLR + Silverlight-Laufzeit wird als das „Plug-In“ bezeichnet, das alle Benutzer installieren müssen, bevor sie Anwendungen ausführen können. Dieses Plug-In ist für die meisten nutzerzentrierten Anwendungen ausreichend. Wenn eine Anwendung die Verwendung einer SDK-Bibliothek (WCF-Integration oder DLR-Laufzeiten wie z. B. Iron Ruby) oder einer benutzerdefinierten Bibliothek erfordert, muss die Anwendung diese Komponenten in das XAP-Paket packen, damit Silverlight weiß, wie es die erforderlichen Typen zur Laufzeit auflösen kann. (Weitere Informationen zu XAPs finden Sie in der Rubrik Cutting Edge in dieser Ausgabe.)

Die Silverlight-Laufzeit ist ungefähr 4 MB groß und enthält neben CoreCLR-Bibliotheken wie z. B. agcore.dll und coreclr.dll auch die von Anwendungsentwicklern benötigten Bibliotheken. Diese wiederum enthalten die folgenden Basisbibliotheken: mscorlib.dll, System.dll, System.Net.dll, System.Xml.dll und System.Runtime.Serialization.dll. Die Laufzeit, die das Browser-Plug-In unterstützt, wird in der Regel im Verzeichnis „C:\Programme\Microsoft Silverlight\2.0.30930.0\“ installiert. Dies ist das Verzeichnis, das erstellt wird, wenn ein Computer im Rahmen der Webbrowsersitzung Silverlight herunterlädt und installiert.

Entwickler, die Anwendungen auf dem gleichen Computer erstellen und testen, besitzen zwei Kopien der Laufzeit: eine durch das Plug-In installierte Kopie und eine durch die SDK-Installation installierte Kopie. Die letztere Kopie befindet sich im Verzeichnis „C:\Programme\Microsoft SDKs\Silverlight\v2.0\Reference Assemblies“. Diese Kopie wird von Visual Studio-Vorlagen als Teil der Kompilierzeit-Referenzlisten verwendet.

Die Sandbox verhindert die Interaktion der Silverlight-Anwendung mit den meisten lokalen Ressourcen, wie dies bei jeder typischen Webanwendung der Fall ist. Standardmäßig kann eine Silverlight-Anwendung nicht auf ein Dateisystem (außer auf den isolierten Speicher) zugreifen, keine Socketverbindungen herstellen, nicht mit an den Computer angeschlossenen Geräten interagieren und keine Softwarekomponenten installieren. Dies bringt natürlich einige Einschränkungen in der Hinsicht mit sich, welche Arten von Anwendungen auf der Silverlight-Plattform erstellt werden können. Silverlight besitzt jedoch alle notwendigen Bestandteile für die Entwicklung einer datengesteuerten Branchenanwendung für Unternehmen, die in die Back-End-Geschäftsprozesse und -dienste integriert werden muss.

Das Anwendungsszenario

Die Branchenanwendung, die ich hier erstellen werde, besitzt eine Drittanbieter-Anrufskontrollarchitektur, bei der ein zentralisierter Server auf die Infrastruktur einer Nebenstellenanlage zugreift, um Telefone zentral zu kontrollieren. Da ich mich auf Silverlight als grafische Benutzeroberfläche konzentrieren will, werde ich nicht allzu lange auf die Telefonieintegration eingehen. Stattdessen werde ich einen einfachen Anrufsimulator verwenden, um ein Anrufseingangsereignis zu generieren. Der Simulator wird ein Datenpaket, das den Anruf repräsentiert, in einer Warteschlange des Anrufmanagers ablegen, wodurch der Prozess ausgelöst wird, der im Mittelpunkt dieses Projekts steht.

Mein fiktives Szenario erfordert, dass die Callcenteranwendung innerhalb eines Webbrowsers plattformunabhängig ausgeführt wird und zugleich als Desktopanwendung eine reichhaltige Benutzerinteraktion bietet. Silverlight war die naheliegendste Wahl, da ActiveX in Nicht-Windows-Clientumgebungen nicht sehr beliebt ist.

Betrachten wir die architektonischen Aspekte der Anwendung. Sie werden Pushbenachrichtigungen, Ereignisintegration, Geschäftsdienstintegration, Zwischenspeicherung, Sicherheit sowie Integration in die Clouddienste implementieren.

Pushbenachrichtigungen Diese Benachrichtigungen werden benötigt, weil das System das Anrufseingangsereignis erfassen und IVR-Daten (Interactive Voice Response), die vom Anrufer zum Erzeugen einer Bildschirmmeldung eingegeben wurden, übermitteln oder die Informationen des eingehenden Anrufs auf dem Bildschirm der Benutzeroberfläche anzeigen muss. Zusätzlich sollte dem Benutzer eine Möglichkeit gegeben werden, den Anruf anzunehmen oder abzulehnen.

Ereignisstreaming In einer typischen Webanwendung kennt der Webserver alle Geschäftsereignisse, während er die Masse der Geschäftsprozesse abwickelt. Im Fall einer RIA-Anwendung (Rich Internet Application) wird die Geschäftsprozessimplementierung jedoch sowohl von der Anwendung, die innerhalb des Webbrowsers ausgeführt wird, als auch von dem Server, der die Geschäftswebdienste implementiert, gemeinsam genutzt. Dies bedeutet, dass sowohl die Geschäftsereignisse als auch die innerhalb der Silverlight-Anwendung generierten Technologieereignisse durch eine Reihe spezieller Webdienste zum Server gesendet werden müssen.

Beispiele für Geschäftsereignisse liegen in diesem Lösungsfall dann vor, wenn der Benutzer (rep) den Anruf ablehnt („rep rejected the call“) oder den Anruf annimmt („rep accepted the call“). Typische Technologieereignisse sind „Connection to Call Manager TCP server failed“ (Verbindung zum Anrufmanager-TCP-Server fehlgeschlagen) und „Web service exception“ (Webdienstausnahme).

Geschäftsdienstintegration Die Callcenterlösung muss wie jede Branchenanwendung Daten nutzen können, die in einer relationalen Datenbank gespeichert sind. Als Mittel für diese Integration verwende ich Webdienste.

Zwischenspeicherung Um die Benutzerfunktionalität zu erhöhen, werde ich die Informationen sowohl im Speicher als auch auf dem Datenträger lokal zwischenspeichern. Die zwischengespeicherten Informationen können die XML-Dateien umfassen, von denen die Aufforderungsskripte von rep und andere Referenzdaten angegeben werden, die sich nur selten ändern.

Anwendungssicherheit Sicherheit ist für diese Art von Anwendung eine Grundanforderung. Zur Sicherheit gehören Authentifizierung, Autorisierung, Datenschutz für bewegte und ruhende Daten sowie eine auf Benutzerprofilen basierende Datenanpassung.

Integration in die Clouddienste Die Integration in einen cloudbasierten Grundlagendienst wie z. B. einen Speicherdienst erfordert spezielle serverseitige Infrastrukturen. Dadurch kann die Verwendung von Clouddiensten genau überwacht und nach Verantwortlichkeit und Servicelevel eingeschränkt werden.

In Teil 2 dieses Artikels werde ich auf die Integration in Geschäftsdienste, Anwendungssicherheit, domänenübergreifende Richtlinien für Webdienste sowie Anwendungspartitionierung eingehen.

Pushbenachrichtigungen mit Socketserver

Bildschirmmeldungen sind eine der Grundanforderungen einer Callcenteranwendung für die Übertragung des Anrufkontexts aus der Telefonieinfrastruktur auf den Bildschirm eines Mitarbeiters. Der übertragene Anrufkontext kann alle Informationen umfassen, die vom anrufenden Kunden gesprochen (bei IVR-Systemen) oder eingegeben wurden.

Die Benachrichtigung kann innerhalb des Browsers auf eine von zwei Arten zur Silverlight-Anwendung gesendet werden: durch Clientabruf oder durch Serverpush. Das Abrufen lässt sich recht einfach implementieren, ist aber möglicherweise bei Callcenterszenarios, in denen die Statussynchronisierung zwischen den Telefonieereignissen und der Clientanwendung exakt sein muss, nicht die optimale Lösung. Aus diesem Grund werde ich mithilfe von Silverlight-Sockets Pushbenachrichtigungen verwenden.

Eine der Hauptfunktionen von Silverlight ist die Kommunikation mit TCP-Sockets. Aus Sicherheitsgründen erlaubt Silverlight Verbindungen zu den Serverports lediglich im Bereich von 4502 bis 4532. Dies ist nur eine der zahlreichen Sicherheitsrichtlinien, die in der Sandbox implementiert sind. Eine weitere wichtige Sandboxrichtlinie besteht darin, dass Silverlight kein Listener sein kann und daher keine eingehenden Socketverbindungen akzeptieren kann. Aus diesen Gründen werde ich einen Socketserver erstellen, der Port 4530 abfragt, und für jede Verbindung, die einen aktiven Callcentermitarbeiter repräsentiert, einen Pool von Verbindungen verwalten.

Die Silverlight-Socketlaufzeit setzt auch domänenübergreifend auf dem Server optionale Richtlinien für alle Socketverbindungen durch. Wenn Silverlight-Anwendungscode versucht, bei einer zugelassenen Portnummer eine Verbindung zu einem IP-Endpunkt zu öffnen, ohne dass der Benutzercode transparent ist, stellt die Laufzeit eine Verbindung zu einem IP-Endpunkt an derselben IP-Adresse mit der Portnummer 943 her. Diese Portnummer ist in der Silverlight-Implementierung festgeschrieben und kann weder durch Anwendungen konfiguriert noch durch den Anwendungsentwickler geändert werden.

Abbildung 1 zeigt die Position des Richtlinienservers in der Architektur. Wenn Socket.ConnectAsync aufgerufen wird, entspricht die Nachrichtenflusssequenz der in Abbildung 3 dargestellten Abfolge. Die Nachrichten 2, 3 und 4 sind absichtlich so konzipiert, dass sie für den Benutzercode nicht transparent sind.

fig03.gif

Abbildung 3 Silverlight-Laufzeit fordert für Socketverbindungen automatisch eine domänenübergreifende Richtlinie an

Ich muss einen Richtlinienserver an derselben IP-Adresse wie den Anrufmanagerserver implementieren. Ich könnte beide Server in einem einzigen Betriebssystemprozess implementieren, aber um die Sache einfach zu halten, werde ich sie in zwei separaten Konsolenprogrammen implementieren. Diese Konsolenprogramme können problemlos in Windows-Dienste umgewandelt und für Failover clusterorientiert gemacht werden, um Zuverlässigkeit und Verfügbarkeit zu gewährleisten.

Asynchrone E/A-Schleifen

Mit .NET Framework 3.5 wurde für Sockets eine neue API zur asynchronen Programmierung eingeführt. Es handelt sich dabei um alle Methoden, die auf Async() enden. Bei den Methoden, die ich auf dem Server verwenden werde, handelt es sich um die Methoden „Socket.AcceptAsync“, „Socket.SendAsync“ und „Socket.ReceiveAsync“. Async-Methoden sind durch Verwendung von E/A-Abschlussports für Serveranwendungen mit hohem Durchsatz und durch Verwendung der wiederverwendbaren SocketAsyncEventArgs-Klasse für eine effektive Empfangs- und Sendepufferverwaltung optimiert.

Da es Silverlight nicht erlaubt ist, TCP-Listener zu erstellen, unterstützt seine Socket-Klasse lediglich ConnectAsync, SendAsync und ReceiveAsync. Silverlight unterstützt lediglich ein asynchrones Programmiermodell, was nicht nur für die Socket-API, sondern für jede Netzwerkinteraktion gilt.

Da ich sowohl auf dem Server als auch auf dem Client ein asynchrones Programmiermodell verwenden werde, sollten wir uns mit den Entwurfsmustern vertraut machen. Ein häufig wiederkehrendes Entwurfsmuster ist die E/A-Schleife, die auf alle Async-Vorgänge angewendet werden kann. Zuerst werde ich die typische synchrone Ausführung der Socketannahmeschleife untersuchen, die hier zu sehen ist:

_listener.Bind(localEndPoint);
 _listener.Listen(50);
 while (true)
 {
    Socket acceptedSocket = _listener.Accept();
    RepConnection repCon = new 
      RepConnection(acceptedSocket);
    Thread receiveThread = new Thread(ReceiveLoop);
    receiveThread.Start(repCon);
 }

Die synchrone Annahme ist intuitiv und einfach zu programmieren und zu verwalten. Diese Implementierung ist jedoch nicht wirklich für Server skalierbar, da es für jede Clientverbindung dedizierte Threads gibt. Diese Implementierung kann leicht mit nur wenigen Verbindungen ihre Maximalleistung erreichen, falls diese Verbindungen stark genutzt werden.

Damit Silverlight gut mit der Browserlaufzeitumgebung arbeiten kann, muss es die Ressourcen möglichst wenig stören. Alle Anrufe im zuvor gezeigten „Socketannahme“-Pseudocode blockieren die Threads, auf denen sie ausgeführt werden, und haben daher negative Auswirkungen auf die Skalierbarkeit. Aus diesem Grund verhält sich Silverlight in Bezug auf blockierende Anrufe sehr einschränkend und gestattet nur die asynchrone Interaktion mit Netzwerkressourcen. Asynchrone Schleifen erfordern von Ihnen, Ihr geistiges Modell anzupassen, um sich einen unsichtbaren Briefkasten vorzustellen, in dem sich jederzeit mindestens eine Anforderung befinden muss, damit die Schleife funktionieren kann.

Abbildung 4 zeigt eine Empfangsschleife (eine vollständigere Implementierung ist im Codedownload enthalten). Es gibt keine Endlosschleifen wie zum Beispiel die Programmierkonstrukte des Typs „while (true)“, die Sie an früherer Stelle im Pseudocode zur synchronen Socketannahme gesehen haben. Es ist für einen Silverlight-Entwickler sehr wichtig, sich an diese Art der Programmierung zu gewöhnen. Damit die Empfangsschleife weiterhin Daten empfangen kann, nachdem eine Nachricht empfangen und verarbeitet wurde, muss in der Warteschlange für den E/A-Abschlussport, der dem angeschlossenen Socket zugeordnet ist, mindestens eine Anforderung enthalten sein. Eine typische asynchrone Schleife ist in Abbildung 5 zu sehen. Sie kann auf ConnectAsync, ReceiveAsync und SendAsync angewendet werden. Dieser Liste kann auf dem Server, auf dem Sie .NET Framework 3.5 verwenden, AcceptAsync hinzugefügt werden.

Abbildung 4 Asynchrone Sende-/Empfangsschleifen mit Silverlight-Sockets

public class CallNetworkClient
{
   private Socket _socket;
   private ReceiveBuffer _receiveBuffer;

   public event EventHandler<EventArgs> OnConnectError;
   public event EventHandler<ReceiveArgs> OnReceive;
   public SocketAsyncEventArgs _receiveArgs;
   public SocketAsyncEventArgs _sendArgs;
//removed for space
    public void ReceiveAsync()
    {
       ReceiveAsync(_receiveArgs);
    }

    private void ReceiveAsync(SocketAsyncEventArgs recvArgs)
    {
       if (!_socket.ReceiveAsync(recvArgs))
       {
          ReceiveCallback(_socket, recvArgs);
       }
    }

    void ReceiveCallback(object sender, SocketAsyncEventArgs e)
    {
      if (e.SocketError != SocketError.Success)
      {
        return;
      }
      _receiveBuffer.Offset += e.BytesTransferred;
      if (_receiveBuffer.IsMessagePresent())
      {
        if (OnReceive != null)
        {
           NetworkMessage msg = 
                       NetworkMessage.Deserialize(_receiveBuffer.Buffer);
           _receiveBuffer.AdjustBuffer();
           OnReceive(this, new ReceiveArgs(msg));
        }
      }
      else
      {
        //adjust the buffer pointer
        e.SetBuffer(_receiveBuffer.Offset, _receiveBuffer.Remaining);
      }
      //queue an async read request
      ReceiveAsync(_receiveSocketArgs);
    }
    public void SendAsync(NetworkMessage msg) { ... }

    private void SendAsync(SocketAsyncEventArgs sendSocketArgs)  
    { 
    ... 
    }

     void SendCallback(object sender, SocketAsyncEventArgs e)  
    { 
    ... 
    }
   }

fig05.gif

Abbildung 5 Asynchrones Socketschleifenmuster

In der Empfangsschleifenimplementierung, die in Abbildung 4 gezeigt wird, handelt es sich bei ReceiveAsync um einen Wrapper für die eintrittsinvariante Methode „ReceiveAsync(SocketAsyncEventArgs recvArgs)“, von der die Anforderung am E/A-Abschlussport des Sockets in die Warteschlange eingereiht wird. Die Methode „SocketAsyncEventArgs“, die in .NET Framework 3.5 eingeführt wurde, spielt in der Silverlight-Socketimplementierung eine ähnliche Rolle und kann bei mehreren Anforderungen wiederverwendet werden, um die Garbage Collection-Änderung zu vermeiden. Es ist Aufgabe der Rückrufroutine, die Nachricht zu extrahieren, ein Nachrichtenverarbeitungsereignis auszulösen und das nächste Empfangselement in die Warteschlange einzureihen, um die Schleife fortzusetzen.

Um Fälle eines partiellen Nachrichtenempfangs zu verarbeiten, passt ReceiveCallback den Puffer an, bevor eine weitere Anforderung in die Warteschlange eingereiht wird. NetworkMessage wird in eine Instanz von ReceiveArgs gewrappt und dem externen Ereignishandler übergeben, um die empfangene Nachricht zu verarbeiten.

Der Puffer wird bei jedem vollständigen Empfang von NetworkMessage zurückgesetzt, nachdem die partielle Nachricht (sofern vorhanden) an den Anfang des Puffers kopiert wurde. Ein ähnlicher Entwurf wird auf dem Server verwendet, aber echte Implementierungen können von zirkulären Puffern profitieren.

Um das im Sequenzdiagramm (siehe Abbildung 6) dargestellte Anrufannahmeszenario zu implementieren, müssen Sie eine erweiterbare Nachrichtenarchitektur erstellen, die Ihnen ermöglicht, Nachrichten mit beliebigem Inhalt zu serialisieren und zu deserialisieren, ohne die Serialisierungslogik für jede neue Nachricht neu schreiben zu müssen.

fig06.gif

Abbildung 6 Layout der serialisierten NetworkMessage-Typen

Die Nachrichtenarchitektur ist recht einfach: Jedes untergeordnete Objekt von NetworkMessage deklariert seine Signatur zum Zeitpunkt der Instanziierung mit der geeigneten MessageAction-Methode. Bei Silverlight und .NET Framework 3.5 (auf dem Server) funktionieren Implementierungen von NetworkMessage.Serialize und NetworkMessage.Deserialize aufgrund der Quellcodeebenenkompatibilität. Die serialisierte Nachricht hat das in Abbildung 6 gezeigte Layout.

Statt am Anfang der Nachricht die Länge einzufügen, könnten Sie die Markierungen „begin“ und „end“ mit entsprechenden Escapesequenzen verwenden. Die Länge in die Nachricht zu kodieren, ist beim Verarbeiten der Puffer viel einfacher.

Die ersten vier Bytes jeder serialisierten Nachricht enthalten die Anzahl der Bytes des serialisierten Objekts, die auf die 4 Bytes folgen. Silverlight unterstützt XmlSerializer innerhalb von System.Xml.dll, das Bestandteil des Silverlight SDK ist. Der Serialisierungscode ist im Codedownload enthalten. Sie werden bemerken, dass er keine direkten Abhängigkeiten von den untergeordneten Klassen wie z. B. RegisterMessage oder anderen Nachrichten einschließlich UnregisterMessage und AcceptMessage enthält. Eine Reihe von XmlInclude-Anmerkungen wird dem Serialisierer helfen, beim Serialisieren der untergeordneten Klassen die .NET-Typen richtig aufzulösen.

In Abbildung 4 wird die Verwendung von NetworkMessage.Serialize und NetworkMessage.Deserialize in ReceiveCallback und SendAsync demonstriert. In der Empfangsschleife wird die eigentliche Nachrichtenverarbeitung vom Ereignishandler durchgeführt, der mit dem NetworkClient.OnReceive-Ereignis verknüpft ist. Ich hätte die Nachricht innerhalb von CallNetworkConnection verarbeiten können, aber den Empfangshandler für die Verarbeitung der Nachricht zu programmieren, fördert die Erweiterbarkeit, indem zur Entwurfszeit der Handler von CallNetworkConnection abgekoppelt wird.

Abbildung 7 zeigt die Silverlight-Anwendung „RootVisual“, von der CallNetworkClient (siehe Abbildung 4) gestartet wird. Alle Silverlight-Steuerelemente werden mit einem einzelnen Benutzeroberflächenthread verknüpft, und alle Benutzeroberflächenaktualisierungen können nur durchgeführt werden, wenn der Code im Kontext dieses Benutzeroberflächenthreads ausgeführt wird. Das asynchrone Programmiermodell von Silverlight führt den Netzwerkzugriffcode und die Verarbeitungshandler auf den Arbeitsthreads des Threadpools aus. Alle von FrameworkElement abgeleiteten Klassen (z. B. Control, Border und die meisten Benutzeroberflächenelemente) erben die Dispatcher-Eigenschaft (von DispatcherObject), die Code auf dem Benutzeroberflächenthread ausführen kann.

In Abbildung 7 aktualisiert „case MessageType.RegisterResponse“ die Benutzeroberfläche durch einen anonymen Delegaten mit den Schichtdetails des Callcentermitarbeiters. Die aus der Ausführung des Delegaten resultierende aktualisierte Benutzeroberfläche ist in Abbildung 8 zu sehen.

Abbildung 7 Silverlight-UserControl-Element, das die eingehenden Nachrichten verarbeitet

public partial class Page : UserControl
{
  public Page()
  {
    InitializeComponent();
    ClientGlobals.socketClient = new CallNetworkClient();
    ClientGlobals.socketClient.OnReceive += new 
                         EventHandler<ReceiveArgs>(ReceiveCallback);
    ClientGlobals.socketClient.Connect(4530);
    //code omitted for brevity
  }
  void ReceiveCallback(object sender, ReceiveArgs e)
  {
    NetworkMessage msg = e.Result;
    ProcessMessage(msg);
  }
  void ProcessMessage(NetworkMessage msg)
  {
    switch(msg.GetMessageType())
    {
      case MessageAction.RegisterResponse:
           RegisterResponse respMsg = msg as RegisterResponse;
           //the if is unncessary as the code always executes in the 
           //background thread
           this.Dispatcher.BeginInvoke(
              delegate()
              {
                 ClientGlobals.networkPopup.CloseDialog();
                 this.registrationView.Visibility = Visibility.Collapsed;
                 this.callView.Visibility = Visibility.Visible;
                 this.borderWaitView.Visibility = Visibility.Visible;
                 this.tbRepDisplayName.Text = this.txRepName.Text;
                 this.tbRepDisplayNumber.Text = respMsg.RepNumber;
                 this.tbCallServerName.Text = 
                                      respMsg.CallManagerServerName;
                 this.tbCallStartTime.Text = 
                                respMsg.RegistrationTimestamp.ToString(); 
              });
            break;
      case MessageAction.Call:
           CallMessage callMsg = msg as CallMessage;
       //Code omitted for brevity
           if (!this.Dispatcher.CheckAccess())
           {
              this.Dispatcher.BeginInvoke(
                 delegate()
                 { 
                    ClientGlobals.notifyCallPopup.ShowDialog(true); 
                 });
           }
           break;
           //
           //Code omitted for brevity  
           //
      default:
             break;
    }
  }
}

fig08.gif

Abbildung 8 Registrierung beim Callcenterserver wird durchgeführt

fig08.gif

Abbildung 9 Bildschirm zur Registrierung des Mitarbeiters

Modale Dialogfelder in Silverlight

Wenn sich ein Callcentermitarbeiter anmeldet, wird er gebeten, die Schicht zu starten, indem er sich beim Callcenterserver registriert. Der Registrierungsprozess auf dem Server speichert die durch die Mitarbeiternummer indizierte Sitzung. Diese Sitzung wird für nachfolgende Bildschirmmeldungen und weitere Benachrichtigungen verwendet. Der Bildschirmübergang der Callcenteranwendung für den Registrierungsprozess ist in den Abbildungen 8 und 9 zu sehen. Ich verwende ein modales Dialogfeld, das den Verlauf des Netzwerksendeprozesses zeigt. Typische Unternehmensbranchenanwendungen arbeiten nach Belieben mit modalen und nicht modalen Popupdialogfeldern. Da im Silverlight SDK kein integriertes Dialogfeld enthalten ist, werde ich Ihnen zeigen, wie Sie in Silverlight ein Dialogfeld entwickeln können, um es in dieser Anwendung zu verwenden.

Vor Silverlight gab es kein einfaches Verfahren, um ein modales Dialogfeld zu erstellen, da es keine einfache Möglichkeit gab, um zu verhindern, dass Tastaturereignisse in die Benutzeroberfläche geleitet werden. Die Mausinteraktion kann indirekt deaktiviert werden, indem „UserControl.IsTestVisible = false“ festgelegt wird. Indem mit der RC0-Einstellung „Control.IsEnabled = false“ gestartet wird, kann verhindert werden, dass Steuerelemente der Benutzeroberfläche Tastatur- oder Mausereignisse empfangen. Ich verwende System.Windows.Controls.Primitives.Popup, um die Dialogfeld-Benutzeroberfläche vor dem vorhandenen Steuerelement anzuzeigen.

Abbildung 10 zeigt ein Basis-SLDialogBox-Steuerelement mit den abstrakten Methoden „GetControlTree“, „WireHandlers“ und „WireUI“. Diese Methoden werden durch die untergeordneten Klassen überschrieben (siehe Abbildung 11). Primitives.Popup erfordert eine Steuerelementinstanz, die nicht Bestandteil der Steuerelementstruktur ist, der das Popup zugeordnet wird. Im Code in Abbildung 10 deaktiviert die Methode „ShowDialog(true)“ die gesamte Steuerelementstruktur auf rekursive Weise, damit keines der darin enthaltenen Steuerelemente Maus- oder Tastaturereignisse empfängt. Da mein Popupdialogfeld interaktiv sein muss, muss Popup.Child von einer neuen Steuerelementinstanz aus festgelegt werden. Die GetControlTree-Implementierung in untergeordneten Klassen fungiert als Steuerelementfactory und stellt eine neue Instanz eines für die Benutzeroberflächenanforderungen des Dialogfelds geeigneten Benutzersteuerelements bereit.

Abbildung 10 Popupdialogfeld in Silverlight

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;

namespace SilverlightPopups
{
    public abstract class SLDialogBox
    {
        protected Popup _popup = new Popup();
        Control _parent = null;
        protected string _ caption = string.Empty;
        public abstract UIElement GetControlTree();
        public abstract void WireHandlers();
        public abstract void WireUI();

        public SLDialogBox(Control parent, string caption)
        {
            _parent = parent;
            _ caption = caption;
            _popup.Child = GetControlTree();
            WireUI();
            WireHandlers();
            AdjustPostion();

        }
        public void ShowDialog(bool isModal)
        {
            if (_popup.IsOpen)
                return; 
            _popup.IsOpen = true;
            ((UserControl)_parent).IsEnabled = false;
        }
        public void CloseDialog()
        {
            if (!_popup.IsOpen)
                return; 
            _popup.IsOpen = false;
            ((UserControl)_parent).IsEnabled = true;
        }
        private void AdjustPostion()
        {
            UserControl parentUC = _parent as UserControl;
            if (parentUC == null) return; 

            FrameworkElement popupElement = _popup.Child as FrameworkElement;
            if (popupElement == null) return;

            Double left = (parentUC.Width - popupElement.Width) / 2;
            Double top = (parentUC.Height - popupElement.Height) / 2;
            _popup.Margin = new Thickness(left, top, left, top);
        }
    }
}

Abbildung 11 NotifyCallPopup.xaml-Design

//XAML Skin for the pop up
<UserControl 
  xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation" 
  xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml" 
  Width="200" Height="95">
   <Grid x:Name="gridNetworkProgress" Background="White">
     <Border BorderThickness="5" BorderBrush="Black">
       <StackPanel Background="LightGray">
          <StackPanel>
             <TextBlock x:Name="tbCaption" HorizontalAlignment="Center" 
                        Margin="5" Text="&lt;Empty Message&gt;" />
             <ProgressBar x:Name="progNetwork" Margin="5" Height="15" 
                        IsIndeterminate="True"/>
          </StackPanel>
          <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" >
             <Button x:Name="btAccept"  Margin="10,10,10,10"  
                      Content="Accept" HorizontalAlignment="Center"/>
             <Button x:Name="btReject"  Margin="10,10,10,10"  
                     Content="Reject" HorizontalAlignment="Center"/>
          </StackPanel>
       </StackPanel>
      </Border>
    </Grid>
</UserControl>

GetControlTree kann implementiert werden, um ein Silverlight-Benutzersteuerelement namens „UserControl“ zu instanziieren, das ins Anwendungspaket kompiliert wird. Stattdessen kann auch mithilfe von XamlReader.LoadControl aus einer XAML-Datei (eXtensible Application Markup Language) ein Steuerelement erstellt werden. In der Regel können Dialogfelder problemlos in Designs implementiert werden, mit denen die kompilierten Handler zur Laufzeit verknüpft werden können. Abbildung 11 zeigt ein XAML-Design, das die Schaltflächen „btAccept“ und „btReject“ besitzt. Die LoadControl-Methode löst eine Ausnahme aus, wenn Sie in der XAML-Datei das Klassenattribut (<userControl class="AdvCallCenter.NotifyCallPopup"…>…</UserControl>) hinter Ihrer Microsoft Expression Studio- oder Visual Studio-Entwurfsaufgabe stehen lassen. Alle Ereignishandlerattribute für die Benutzeroberfläche müssen entfernt werden, um mithilfe von LoadControl eine erfolgreiche Analyse durchzuführen.

Um Designs zu erstellen, können Sie dem Projekt das Silverlight-Element „UserControl“ hinzufügen, es in Expression entwickeln und, sofern vorhanden, die Klassenattribut- und Ereignishandlernamen, die mit den Steuerelementen aus der XAML-Datei verknüpft sind, entfernen. Die Klickhandler können, wie in Abbildung 12 gezeigt, Teil der untergeordneten Popupklasse sein. Alternativ kann eine separate Handlerbibliothek erstellt werden, die mittels Reflektion mit den Steuerelementen verknüpft werden kann.

Abbildung 12 NotifyCallPopup-Implementierung

public class NotifyCallPopup : SLDialogBox
{
   public event EventHandler<EventArgs> OnAccept;
   public event EventHandler<EventArgs> OnReject;
   public NotifyCallPopup(Control parent, string msg)
        : base(parent, msg)
   {
   }
   public override UIElement GetControlTree()
   {
      Return SLPackageUtility.GetUIElementFromXaml("NotifyCallPopup.txt");
   }
   public override void WireUI()
   {
      FrameworkElement fe = (FrameworkElement)_popup.Child;
      TextBlock btCaption = fe.FindName("tbCaption") as TextBlock;
      if (btCaption != null)
          btCaption.Text = _caption;
      }
   public override void WireHandlers()
   {
      FrameworkElement fe = (FrameworkElement)_popup.Child;
      Button btAccept = (Button)fe.FindName("btAccept");
      btAccept.Click += new RoutedEventHandler(btAccept_Click);

      Button btReject = (Button)fe.FindName("btReject");
      btReject.Click += new RoutedEventHandler(btReject_Click);
   }

   void btAccept_Click(object sender, RoutedEventArgs e)
   {
      CloseDialog();
      if (OnAccept != null)
          OnAccept(this, null);
   }
   void btReject_Click(object sender, RoutedEventArgs e)
   {
      CloseDialog();
      if (OnReject != null)
         OnReject(this, null);
   }
}

Die Handler können in jedem Silverlight-Bibliotheksprojekt enthalten sein, da sie als Ergebnis der Projektabhängigkeit automatisch ins XAP-Paket kompiliert werden. Um die Designdateien in das XAP-Paket aufzunehmen, fügen Sie sie als XML-Dateien dem Silverlight-Projekt hinzu, und ändern Sie ihre Erweiterung in „XAML“. Die standardmäßige Erstellungsaktion für Dateien mit einer XAML-Erweiterung besteht darin, sie in die DLL-Datei der Anwendung zu kompilieren. Da ich möchte, dass diese Dateien als Textdateien verpackt werden, müssen in den Eigenschaftenfenstern die folgenden Attribute festgelegt werden:

  • BuildAction = "Content"
  • Copy to Output Directory = "Do Not Copy"
  • Custom Tool = <jeden vorhandenen Wert löschen>

Der XAML-Parser (XamlReader.Load) berücksichtigt die Erweiterung zwar nicht, aber die Verwendung der Erweiterung „.xaml“ ist trotzdem intuitiver und repräsentiert den Inhalt besser. SLDialogBox ist nur für das Einblenden und Schließen des Dialogfelds zuständig. Die untergeordneten Implementierungen werden angepasst, um den Anforderungen der Anwendung zu entsprechen.

Implementierung von Pushbenachrichtigungen

Eine Callcenteranwendung muss in der Lage sein, eine Bildschirmmeldung mit den Anruferdaten anzuzeigen. Ein Callcenterarbeitstag beginnt mit der Registrierung des Mitarbeiters beim Callcenterserver. Pushbenachrichtigungen werden mit verbindungsorientierten Sockets implementiert. Die gesamte Implementierung des Anrufmanagerservers wird in den Abbildungen nicht gezeigt, steht aber im Codedownload zur Verfügung. Wenn der Silverlight-Client eine Socketverbindung auf dem Server herstellt, wird RepList ein neues RepConnection-Objekt hinzugefügt. RepList ist eine generische Liste, die mit einer eindeutigen Mitarbeiternummer indiziert ist. Wenn ein Anruf eingeht, verwenden Sie diese Liste, um einen verfügbaren Mitarbeiter zu finden und mithilfe der Socketverbindung, die mit RepConnection verknüpft ist, den Mitarbeiter mit den Anrufinformationen zu benachrichtigen. RepConnection verwendet ReceiveBuffer (siehe Abbildung 13).

Abbildung 13 RepConnection verwendet ReceiveBuffer

class SocketBuffer
{
  public const int BUFFERSIZE = 5120;
  protected byte[] _buffer = new byte[BUFFERSIZE]
  protected int _offset = 0;
  public byte[] Buffer
  {
    get { return _buffer; }
    set { _buffer = value; }
  }

 //offset will always indicate the length of the buffer that is filled
  public int Offset
  {
    get {return _offset ;}
    set { _offset = value; }
  }

  public int Remaining
  {
    get { return _buffer.Length - _offset; }
  }
}
class ReceiveBuffer : SocketBuffer
{
  //removes a serialized message from the buffer, copies the partial message
  //to the beginning and adjusts the offset
  public void AdjustBuffer()
  {
    int messageSize = BitConverter.ToInt32(_buffer, 0);
    int lengthToCopy = _offset - NetworkMessage.LENGTH_BYTES - messageSize;
    Array.Copy(_buffer, _offset, _buffer, 0, lengthToCopy);
    offset = lengthToCopy;
  }
  //this method checks if a complete message is received
  public bool IsMessageReceived()
  {
    if (_offset < 4)
       return false;
    int sizeToRecieve = BitConverter.ToInt32(_buffer, 0);
    //check if we have a complete NetworkMessage
    if((_offset - 4) < sizeToRecieve)
      return false; //we have not received the complete message yet
    //we received the complete message and may be more
      return true;
   }
 }

Sie werden mit einem Silverlight-Anrufsimulator einen Anruf an CallDispatcher._callQueue senden, um den Bildschirmmeldungsprozess auszulösen. CallDispatcher ist in keiner der Abbildungen zu sehen, steht aber im herunterladbaren Code zur Verfügung. Es verknüpft einen Handler mit _callQueue.OnCallReceived und wird benachrichtigt, wenn der Simulator innerhalb der ProcessMessage-Implementierung die Nachricht in die _callQueue-Warteschlange einreiht. Mithilfe der oben behandelten Popupdialogfelder zeigt der Client die in Abbildung 14 dargestellte Annahme-/Ablehnungsbenachrichtigung an. Das Benachrichtigungsdialogfeld aus Abbildung 8 wird durch die folgende Codezeile angezeigt:

ClientGlobals.notifyCallPopup.ShowDialog(true);  

fig14.gif

Abbildung 14 Benachrichtigung über eingehende Anrufe

Domänenübergreifender Zugriff auf TCP-Dienste

Im Unterschied zu den Medien- und Werbungsanzeigeanwendungen erfordern echte Branchenanwendungen für Unternehmen die Integration in mehrere verschiedene Diensthostumgebungen. So wird beispielsweise die Callcenteranwendung auf einer Website (advcallclientweb, das bei localhost:1041 gehostet ist) gehostet, verwendet für Bildschirmmeldungen einen statusbehafteten Socketserver in einer anderen Domäne (localhost:4230) und erreicht Branchendaten durch Dienste, die in einer weiteren Domäne (localhost:1043) gehostet werden. Zum Übertragen von Instrumentationsdaten verwendet diese Anwendung wieder eine andere Domäne.

Die Silverlight-Sandbox erlaubt standardmäßig keinen Netzwerkzugriff auf eine andere Domäne außer der Ursprungsdomäne „advcallclientweb“ (localhost:1041). Wenn ein derartiger Netzwerkzugriff entdeckt wird, überprüft die Silverlight-Laufzeit die optionalen Richtlinien, die von der Zieldomäne eingerichtet wurden. Hier ist eine typische Liste der Diensthostszenarios, die domänenübergreifende Richtlinienanforderungen des Clients unterstützen müssen:

  • In der Cloud gehostete Dienste
  • In einem Dienstprozess gehostete Webdienste
  • In IIS oder anderen Webservern gehostete Webdienste
  • HTTP-Ressourcen wie XAML-Markup- und XAP-Pakete
  • In einem Dienstprozess gehostete TCP-Dienste

Obwohl es einfach ist, domänenübergreifende Richtlinien für HTTP-Ressourcen und Webdienstendpunkte zu implementieren, die in IIS gehostet werden, erfordern die anderen Fälle die Kenntnis der Semantik von Richtlinienanforderungen und -antworten. In diesem Abschnitt werde ich kurz die für den in Abbildung 1 als Anrufmanager bezeichneten TCP-Bildschirmmeldungsserver erforderliche Richtlinieninfrastruktur implementieren. Die anderen domänenübergreifenden Szenarios werden in Teil 2 dieses Artikels behandelt.

Domänenübergreifende Richtlinien mit TCP-Diensten

Jeder TCP-Dienstzugriff in Silverlight wird als domänenübergreifende Anforderung betrachtet, und der Server muss einen TCP-Listener an derselben IP-Adresse implementieren, die an Port 943 gebunden ist. Der in Abbildung 3 dargestellte Richtlinienserver ist der Listener, der für diesen Zweck implementiert wurde. Dieser Server implementiert einen Anforderung/Antwort-Prozess, um deklarative Richtlinien zu generieren, die von der Silverlight-Laufzeit benötigt werden, bevor dem Netzwerkstapel auf dem Client erlaubt wird, eine Verbindung zum Bildschirmmeldungsserver (dem Anrufmanager in Abbildung 3) herzustellen.

Um die Sache einfach zu halten, hoste ich den Anrufmanagerserver in einer Konsolenanwendung. Diese Konsolenanwendung kann bei echten Implementierungen problemlos in einen Windows-Dienst umgewandelt werden. Abbildung 3 demonstriert die typische Interaktion mit einem Richtlinienserver, bei der die Silverlight-Laufzeit auf Port 943 eine Verbindung zum Server herstellt und eine Richtlinienanforderung sendet, die eine einzige Textzeile enthält: <policy-file-request/>.

Die XML-basierten Richtlinien ermöglichen das in Abbildung 3 gezeigte Szenario. Der Socketressourcenabschnitt kann eine Gruppe von Ports innerhalb des zulässigen Bereichs von 4502 bis 4534 festlegen. Das Ziel der Einschränkung auf einen Bereich besteht darin, die Angriffsfläche zu minimieren und dadurch das Risiko zufälliger Schwächen in der Firewallkonfiguration zu verringern. Da der Callcenterserver (der Anrufmanager in Abbildung 1) Portnummer 4530 abfragt, wird die Socketressource folgendermaßen konfiguriert:

<access-policy>
   <policy>
     <allow-from> list of URIs</allow-from>
     <grant-to> <socket-resource port="4530" protocol="tcp"/></grant-to>
  </policy>     
</access-policy>

Sie können <socket-resource> auch dafür konfigurieren, alle zulässigen Portnummern zu erlauben, indem Sie port="4502–4534" festlegen.

Um Zeit zu sparen, verwende ich den Code des Anrufmanagerservers dazu, den Richtlinienserver zu implementieren. Ein Silverlight-Client stellt eine Verbindung zum Richtlinienserver her, sendet eine Anforderung und liest die Antwort. Der Richtlinienserver trennt die Verbindung, sobald die Richtlinienantwort erfolgreich gesendet wurde. Die Richtlinieninhalte werden vom Richtlinienserver aus einer lokalen Datei namens „clientaccesspolicy.xml“ gelesen, die im Download enthalten ist.

Die TCP-Listener-Implementierung für den Richtlinienserver ist in Abbildung 15 zu sehen. Dabei wird dasselbe Muster asynchroner Schleifen verwendet, das an früherer Stelle bereits im Zusammenhang mit TCP-Accept behandelt wurde. Die Datei „Clientaccesspolicy.xml“ wird in einen Puffer eingelesen und für das Senden an alle Silverlight-Clients verwendet. ClientConnection kapselt den akzeptierten Socket und den Empfangspuffer, die SocketAsyncEventArgs zugeordnet werden.

Abbildung 15 Implementierung des TCP-Richtlinienservers

class TcpPolicyServer
{
  private Socket _listener;
  private byte[] _policyBuffer;
  public static readonly string PolicyFileName = "clientaccesspolicy.xml";
  SocketAsyncEventArgs _socketAcceptArgs = new SocketAsyncEventArgs();
  public TcpPolicyServer()
  {
    //read the policy file into the buffer
    FileStream fs = new FileStream(PolicyServer.PolicyFileName, 
                        FileMode.Open);
    _policyBuffer = new byte[fs.Length];
    fs.Read(_policyBuffer, 0, _policyBuffer.Length);
    _socketAcceptArgs.Completed += new 
                 EventHandler<SocketAsyncEventArgs>(AcceptAsyncCallback);

  }
  public void Start(int port)
  {

    IPHostEntry ipHostInfo = Dns.GetHostEntry(Dns.GetHostName());
    //Should be within the port range of 4502-4532
    IPEndPoint ipEndPoint = new IPEndPoint(IPAddress.Any, port);

    _listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, 
                                                       ProtocolType.Tcp);

    // Bind the socket to the local endpoint and listen for incoming connections 
    try
    {
      _listener.Bind(ipEndPoint);
      _listener.Listen(50);
      AcceptAsync();
    }
    //code omitted for brevity

   }
   void AcceptAsync()
   {
      AcceptAsync(socketAcceptArgs);
    }

    void AcceptAsync(SocketAsyncEventArgs socketAcceptArgs)
    {
      if (!_listener.AcceptAsync(socketAcceptArgs))
      {
         AcceptAsyncCallback(socketAcceptArgs.AcceptSocket, 
                                             socketAcceptArgs);
      }
    }

    void AcceptAsyncCallback(object sender, SocketAsyncEventArgs e)
    {
      if (e.SocketError == SocketError.Success)
      {
        ClientConnection con = new ClientConnection(e.AcceptSocket, 
                                               this._policyBuffer);
        con.ReceiveAsync();
      }
      //the following is necessary for the reuse of _socketAccpetArgs
      e.AcceptSocket = null;
      //schedule a new accept request
      AcceptAsync();
     }
   }

In dem in Abbildung 15 gezeigten Codebeispiel wird SocketAsyncEventArgs zwischen mehreren TCP-Accepts wiederverwendet. Damit dies funktionieren kann, muss e.AcceptSocket in AcceptAsyncCallback auf null gesetzt werden. Dadurch wird eine Garbage Collection-Änderung auf dem Server mit hohen Skalierbarkeitsanforderungen verhindert.

Zusammenfassung

Die Implementierung der Pushbenachrichtigungen ist ein wichtiger Aspekt einer Aufrufcenteranwendung und ermöglicht den Bildschirmmeldungsprozess. Silverlight macht die Bildschirmmeldungsimplementierung viel einfacher als AJAX oder ähnliche Frameworks. Da sich die Modelle der Server- und der Clientprogrammierung ähneln, konnte ich auf Quellcodeebene ein gewisses Maß an Wiederverwendbarkeit erreichen. Im Fall des Callcenters konnte ich sowohl in der Server- als auch in der Clientimplementierung Nachrichtendefinitionen und Empfangspufferabstraktionen verwenden.

Ich werde in Teil 2 dieser Reihe Webdienstintegration, Sicherheit, Clouddienstintegration und Anwendungspartitionierung vor Ort implementieren. Ich hoffe, dass diese Bemühungen gut zu einigen der Branchenszenarios passen, auf die Sie stoßen werden, und freue mich auf Ihr Feedback.

Ich bedanke mich bei Dave Murray und Shane DeSeranno von Microsoft für ihre Hilfe bei der Silverlight-Socketimplementierung sowie bei Robert Brooks, einem Domänenexperten für Callcenter, für seine Unterstützung bei der Erörterung von Bildschirmmeldungen.

Hanu Kommalapati arbeitet als Plattformstrategieberater bei Microsoft und berät in seiner derzeitigen Position Unternehmenskunden bei der Erstellung skalierbarer Branchenanwendungen auf Silverlight- und Azure Services-Plattformen.