January 2009

Volume 24 Number 01

Silverlight - Build Line-Of-Business Enterprise Apps With Silverlight, Part 1

By Hanu Kommalapati | January 2009

This article discusses:

  • The Silverlight runtime environment
  • Silverlight asynchronous programming
  • Cross-domain policies
  • A sample enterprise application
This article uses the following technologies:
Silverlight 2

Code download available

Contents

Silverlight Fundamentals: the CoreCLR
Silverlight Runtime
The Application Scenario
Push Notifications with Socket Server
Asynchronous I/O Loops
Modal Dialog Boxes in Silverlight
Push Notification Implementation
Cross-Domain Access of TCP Services
Cross-Domain Policies with TCP Services
Wrapping Up

When I presentedan executive briefing on the business implications of Silverlight to a large company in Houston recently, the response was lukewarm. The cool demos that showcased DeepZoom, Picture-In-Picture and HD quality video, and high-quality animations should have easily excited the group. When I polled the audience members about their limited interest, it became clear that while dazzling graphics were great, there was very little real-world guidance available for building enterprise-quality, data-centric, line-of-business (LOB) applications with Silverlight.

Today, enterprise-class applications require secure delivery of LOB information across network boundaries, often over the Internet, with role-based UI and data trimming applied for the business context. Running Silverlight on the client and the Microsoft .NET Framework 3.5 on the server supplies excellent capabilities for building such scalable and secure LOB applications. The lightweight Silverlight runtime running inside a sandbox provides framework libraries for integration with back-office data services. In order to build robust applications with Silverlight, architects and developers need to understand the Silverlight programming model and its framework features in the context of a real-world application.

My main purpose in this article is to take an LOB scenario and build an application from the ground up, illustrating various aspects of Silverlight development along the way. The solution I'll discuss is a call center application; its logical architecture is shown in Figure 1. In this installment, I will focus on screen pop notification, the asynchronous programming model, Silverlight dialog boxes, and cross-domain TCP policy server implementation. In Part 2, I will discuss application security, Web service integration, application partitioning, and a number of other aspects of the application.

Figure 1 Silverlight Call Center Logical Architecture

Silverlight Fundamentals: the CoreCLR

Before I begin, let's have a refresher of Silverlight fundamentals. First I'll take a look inside the Silverlight runtime so you can get a better understanding of what's possible with Silverlight. The CoreCLR is the virtual machine used by Silverlight. It's similar to the CLR that powers the .NET Framework 2.0 and beyond and contains similar type-loading and garbage collection (GC) systems.

The CoreCLR has a very simple code access security (CAS) model—simpler than in the desktop CLR because Silverlight only needs to enforce security policies at the application level. This is because, as a platform-independent Web client, it can't rely on any particular enterprise or machine policies being in effect, and it should not allow a user to change existing policies. There are a few exceptions, such as OpenFileDialog and IsolatedStorage (storage quota change), where Silverlight needs the user's explicit consent for breaking the sandbox default rule set. OpenFileDialog is used for accessing the file system, while IsolatedStorage is used to access the namesake-isolated storage and increase the storage quota.

For desktop applications, each executable loads exactly one copy of the CLR, and the OS process will only contain one application. Each application has a system domain, a shared domain, a default domain, and a multitude of explicitly created AppDomains (see " JIT and Run: Drill into .NET Framework Internals to See How the CLR Creates Runtime Objects"). A similar domain model is found in the CoreCLR. In the case of Silverlight, several applications, possibly from different domains, will be running in the same OS process.

In Internet Explorer 8.0, each tab runs in its own isolated process; thus all the Silverlight applications hosted inside the same tab will be running in the context of the same CoreCLR instance, as illustrated in Figure 2. As each application can be from a different domain of origin, for security reasons, each application will be loaded into its own AppDomain. There will be as many instances of CoreCLR as there are tabs currently hosting Silverlight applications.

Similar to the desktop CLR, each AppDomain will get its own pool of static variables. Each domain-specific pool will be initialized during the AppDomain bootstrapping process.

Figure 2 Each Silverlight App Will Run in Its Own AppDomain

Silverlight applications can't create their own custom application domains; that ability is reserved for internal use. For a more detailed discussion of the CoreCLR, refer to the following CLR Inside Out columns from the CLR team: " Program Silverlight with CoreCLR" and " Security in Silverlight 2."

Silverlight Runtime

Silverlight is designed for a broad range of applications that require varying degrees of framework and library support. A simple application may, for example, just play audio files of a few bytes to help with the pronunciation of words on a dictionary Web site or display a banner ad. On the other hand, enterprise class LOB applications require security, privacy of data, state management, integration with other applications and services, and instrumentation support, to name a few. At the same time, Silverlight needs to have a smaller runtime so that deployment over the Internet will not be a problem on slower connections.

These requirements seem to conflict, but the Silverlight team tackled this by partitioning the framework into the layered view shown in Figure 2. CoreCLR + Silverlight runtime is called the "plug-in," which all users will install before they can run applications. The plug-in is sufficient for most consumer-centric applications. If an application requires the use of an SDK library (WCF integration or DLR runtimes such as Iron Ruby) or a custom library, the application must package these components into the XAP package so that Silverlight will know how to resolve the necessary types at run time (see Cutting Edge in this issuefor more on XAPs).

The Silverlight runtime is approximately 4MB in size and, in addition to CoreCLR libraries such as agcore.dll and coreclr.dll, contains the necessary libraries required by app developers. These include the following fundamental libraries: mscorlib.dll, System.dll, System.Net.dll, System.Xml.dll, and System.Runtime.Serialization.dll. The runtime that supports the browser plug-in typically is installed under the C:\Program Files\Microsoft Silverlight\2.0.30930.0\ directory. This is the directory that gets created when a computer downloads and installs Silverlight as a part of the Web browsing session.

Developers who build and test applications on the same machine will have two copies of the runtime: one copy installed by the plug-in and the other through the SDK install. The latter copy can be located in the directory C:\Program Files\Microsoft SDKs\Silverlight\v2.0\Reference Assemblies. This copy will be used by Visual Studio templates as a part of the compile time references list.

The sandbox prevents the Silverlight application from interacting with most of the local resources, which is true for any typical Web application. By default, a Silverlight application can't access a file system (other than isolated storage), make socket connections, interact with devices attached to the computer, or install software components. This certainly imposes some constraints on the types of applications one could construct on the Silverlight platform. However, Silverlight has all the necessary ingredients for developing an enterprise-class, data-driven LOB application that needs to integrate with the back-end business processes and services.

The Application Scenario

The LOB application I'll be building here demonstrates a third-party call-control architecture where a centralized server taps into a private branch exchange (PBX) infrastructure to centrally control phones. Since my purpose is to focus on Silverlight as the UI surface, I will not be spending too much time on the telephony integration. Instead, I will use a simple call simulator to generate an incoming call event. The simulator will drop a packet of data that represents the call into a waiting queue of the Call Manager, which triggers the process that is central to this project.

My fictitious scenario requires the call center application to run inside a Web browser in a platform-independent manner while at the same time providing rich user interaction as a desktop application. Silverlight was the natural choice, as ActiveX is not very popular on non-Windows client environments.

Let's look at the app's architectural aspects. You'll be implementing push notifications, event integration, business service integration, caching, security, and integration with the cloud services.

Push NotificationsThese are required because the system needs to capture the incoming call event and transfer interactive voice response (IVR) data that was entered by the caller to do a "screen pop," or populating of the UI screen with the incoming call information. In addition, the user should be given an opportunity to accept or reject the call.

Event StreamingIn a typical Web app, the Web server has all the knowledge of the business events as it performs the bulk of the business processes. In the case of a rich Internet application (RIA), however, the business process implementation will be shared by both the application running inside the Web browser and the server that implements business Web services. This means that the business events as well as technology events generated within the Silverlight application need to be sent to the server through a set of special Web services.

Examples of business events in this solution case are when the user (rep) rejects the call ("rep rejected the call") or accepts the call ("rep accepted the call"). Typical technology events are "Connection to Call Manager TCP server failed" and "Web service exception."

Business Service IntegrationThe call center solution, just like any LOB application, needs to be integrated with data that may be stored in a relational database. I will use Web services as a vehicle for this integration.

CachingFor a better user experience, I will cache the information locally in memory as well as on the disk. The cached information may include the XML files that indicate the rep's prompter scripts and other reference data that may not change often.

Security ApplicationSecurity is a fundamental requirement of this kind of application. The security includes authentication, authorization, privacy of data in flight and at rest, and user profile-based data trimming.

Integration with the Cloud ServicesIntegration with a cloud-based foundation service, such as storage service, requires special server-side infrastructures. This is so that the use of cloud services can be closely monitored and throttled for accountability and service levels.

I will cover integration with business services, application security, cross-domain policies for Web services, and application partitioning in Part 2 of this article.

Push Notifications with Socket Server

Screen pop is one of the fundamental requirements of a call center application for transferring call context from the telephony infrastructure to an agent's screen. The transferred call context may include any information spoken (for IVR systems) or keyed in by the phone-in customer.

The notification can be sent to the Silverlight application within the browser in one of two ways: through client polling or server push. Polling is fairly easy to implement, but it may not be the optimal choice for call center scenarios where state synchronization between the telephony events and the client application has to be precise. It is for that reason that I will use push notifications using Silverlight sockets.

One of the important capabilities of Silverlight is communication with TCP sockets. For security reasons, Silverlight only allows the connections to the server ports in the range of 4502 to 4532. This is one of the many security policies implemented in the sandbox. Another important sandbox policy is that Silverlight can't be a listener and, hence, cannot accept inbound socket connections. For these reasons, I am going to create a socket server listening on port 4530 and maintain a pool of connections with each connection representing an active call center rep.

The Silverlight socket runtime also enforces cross-domain opt-in policies on the server for all of the socket connections. When Silverlight application code attempts to open a connection to an IP endpoint on an allowed port number, opaque to the user code, the runtime will make a connection to an IP endpoint on the same IP address with the port number 943. This port number is hardwired to the Silverlight implementation and can't be configured by applications or changed by the application developer.

Figure 1shows where the policy server fits into the architecture. When Socket.ConnectAsync is called, the message flow sequence is as shown in Figure 3. By design, messages 2, 3, and 4 are totally opaque to the user code.

Figure 3 Silverlight Runtime Requests Cross-Domain Policy Automatically for Socket Connections

I need to implement a policy server on the same IP address as the call manager server. I could implement both the servers in a single OS process; however, for simplicity's sake I will implement them in two separate console programs. These console programs can easily be converted to Windows services and made cluster-aware for failover to provide reliability and availability.

Asynchronous I/O Loops

The .NET Framework 3.5 introduced new async programming API to sockets; these are the methods that end with Async(). The methods I will be using on the server are Socket.AcceptAsync, Socket.SendAsync, and Socket.ReceiveAsync. Async methods are optimized for high throughput server applications through the use of I/O Completion Ports and effective receive and send buffer management through the reusable SocketAsyncEventArgs class.

Because of the fact that Silverlight is not allowed to create TCP listeners, its Socket class only supports ConnectAsync, SendAsync, and ReceiveAsync. Silverlight only supports an asynchronous programming model, and that's not just for socket API but for any network interaction.

Since I will be using an async programming model on the server as well as on the client, let's get comfortable with the design patterns. One recurring design pattern is the I/O loop, which is applicable to all the Async operations. First, I will look at the typical synchronous execution of the socket accept loop, as seen here:

_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); }

The sync accept is intuitive and easy to program and maintain; however, this implementation is not really scalable for servers, as there are dedicated threads for each client connection. This implementation can easily peak at a few connections if the connections are very chatty.

For Silverlight to work well with the browser runtime environment, it has to intrude on the resources as little as possible. All of the calls in the "socket accept" pseudo code shown previously block the threads on which they execute, and thus have a negative impact on scalability. For that reason, Silverlight is very restrictive on blocking calls and, in fact, allows only asynchronous interaction with network resources. Asynchronous loops require adjusting your mental model to envision an invisible message box that should have at least one request inside it all the time for the loop to work.

Figure 4 shows a receive loop (a more complete implementation is in the code download). There are no infinite loop programming constructs like the while (true) loop you saw in the synchronous socket accept pseudo code earlier. Getting used to this kind of programming is vital for a Silverlight developer. In order for the receive loop to continue receiving data, after a message has been received and processed, there should be at least one request in the queue against the I/O Completion Port associated with the connected socket. A typical async loop is illustrated in Figure 5and is applicable to ConnectAsync, ReceiveAsync, and SendAsync. Accept­Async can be added to this list on the server where you will use the .NET Framework 3.5.

Figure 4 Async Send/Receive Loops with 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

Figure 5 Asynchronous Socket Loop Pattern

In the receive loop implementation shown in Figure 4, Receive­Async is a wrapper to the reentrant method ReceiveAsync(Socket­AsyncEventArgs recvArgs) that will queue the request on the socket's I/O completion port. SocketAsyncEventArgs, introduced in the .NET Framework 3.5, has a similar role in the Silverlight socket implementation and can be reused between multiple requests, avoiding the garbage collection churn. It will be the responsibility of the callback routine to extract the message, trigger a message-processing event, and enqueue the next receive item for continuing the loop.

To handle cases of partial message reception, ReceiveCallback adjusts the buffer before queuing another request. Network­Message is wrapped in an instance of ReceiveArgs and passed to the external event handler for processing the received message.

The buffer is reset for every complete receipt of the Network­Message after copying the partial message, if any, to the beginning of the buffer. A similar design is used on the server, but real implementations can benefit from circular buffers.

In order to implement the "Call Acceptance" scenario shown in the sequence diagram (see Figure 6), you need to create an extensible message architecture that allows you to serialize and deserialize messages that contain arbitrary content without necessitating the rewrite of the serialization logic for every new message.

fig06.gif

Figure 6 Layout of the Serialized NetworkMessage Types

The message architecture is rather simple: each child object of the Network­Message declares its signature at the instantiation time with appropriate MessageAction. NetworkMessage.Serialize and Deserialize implementations will work on Silverlight and the .NET Framework 3.5 (on the server) because of the source code-level compatibility. The serialized message will have the layout shown in Figure 6.

Instead of inserting the length at the beginning of the message, you could use the "begin" and "end" markers with appropriate escape sequences. Encoding length into the message is a lot simpler for processing the buffers.

The first four bytes of every serialized message will include the number of bytes of the serialized object following the 4 bytes. Silverlight supports XmlSerializer located inside System.Xml.dll that is part of the Silverlight SDK. The serialization code is included in the code download. You'll note that it does not have any direct dependencies on the child classes, such as RegisterMessage or other messages including UnregisterMessage and AcceptMessage. A series of Xml­Include annotations will help the serializer to properly resolve the .NET types while serializing the child classes.

The use of NetworkMessage.Serlialize and Deserialize is shown in ReceiveCallback and SendAsync in Figure 4. In the receive loop, the actual message processing is done by the event handler attached to the NetworkClient.OnReceive event. I could have processed the message inside CallNetworkConnection, but wiring the receive handler to process the message will help the extensibility by decoupling the handler from CallNetworkConnection at design time.

Figure 7shows the Silverlight application RootVisual, which starts the CallNetworkClient (shown in Figure 4). All Silverlight controls are attached to a single UI thread, and any UI updates can only be done when the code executes in the context of that UI thread. The asynchronous programming model of Silverlight executes the network access code and the processing handlers on the thread pool's worker threads. All of the FrameworkElement derived classes (such as Control, Border, Panel, and most of the UI elements) inherit the Dispatcher property (from Dispatcher­Object), which can execute code on the UI thread.

In Figure 7, the case MessageType.RegisterResponse will update the UI with the agent's call center shift details through an anonymous delegate. The updated UI resulting from the delegate execution is shown in Figure 8.

Figure 7 Silverlight UserControl that Processes the Incoming Messages

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

Figure 8 Rep’s Initial Registration Screen

fig09.gif

Figure 9 Registration with the Call Center Server is in Progress

Modal Dialog Boxes in Silverlight

When a call center rep logs in, he will be asked to start the shift by registering with the call center server. The registration process on the server will save the session indexed by the rep number. This session will be used for subsequent screen pop and other notifications. The screen transition of the call center app for the registration process is shown in Figures 8and 9. I will use a modal dialog box that shows the progress of the network send. Typical enterprise LOB apps use pop-up dialog boxes, modal and non-modal, quite liberally. As there is no built-in DialogBox in the Silverlight SDK, you will see how to develop one in Silverlight for use in this app.

Until Silverlight, there was no simple way to build a modal dialog, as there was no easy way to prevent keyboard events from being pumped into the UI. Mouse interaction can be disabled indirectly by setting UserControl.IsTestVisible = false. Starting from RC0 setting Control.IsEnabled = false prevents UI controls from receiving any keyboard or mouse events. I will use System.Windows.Controls.Primitives.Popup to display the dialog UI on top of the existing control.

Figure 10shows a base SLDialogBox control with the abstract methods GetControlTree, WireHandlers, and WireUI. These methods will be overridden by the child classes, as shown in Figure 11. Primitives.Popup requires a control instance that is not part of the control tree to which the Popup will be attached. In the code in Figure 10, the ShowDialog(true) method will disable the entire control tree recursively so that none of the contained controls will receive any mouse or keyboard events. Since my pop-up dialog must be interactive, the Popup.Child should be set from a new control instance. The GetControlTree implementation in child classes will act as a control factory and provide a new instance of a user control appropriate for the UI requirements of the dialog.

Figure 10 Popup DialogBox 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); } } }

Figure 11 NotifyCallPopup.xaml Skin

//XAML Skin for the pop up <UserControl xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://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 can be implemented to instantiate a Silverlight UserControl that is compiled into the application package, or a control can be created from an eXtensible Application Markup Language (XAML) file using XamlReader.LoadControl. Typically, dialog boxes can easily be implemented in skins to which the compiled handlers can be attached at run time. Figure 11shows a XAML skin that has btAccept and btReject buttons. The LoadControl method will throw an exception if you leave the class attribute (<userControl class="AdvCallCenter.NotifyCallPopup"…>…</UserControl>) in the XAML after your Microsoft Expression Studio or Visual Studio design task. Any UI event handler attributes must be removed for successful parsing using LoadControl.

To create skins, you can add the Silverlight UserControl to the project, design it in Expression, and remove the "class" attribute and event handler names, if any, attached to the controls from the XAML file. The click handlers can be part of the child pop-up class, as shown in Figure 12, or alternatively a separate handler library can be created that can be wired to the controls using reflection.

Figure 12 NotifyCallPopup Implementation

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); } }

The handlers can be in any Silverlight library project, as they will automatically be compiled into the XAP package as a result of the project dependency. In order for the skin files to be included in the XAP package, add them to the Silverlight project as XML files and change the extension to XAML. The default build action for files with a XAML extension is to compile them into the application DLL. Since I want these files to be packaged as text files, the following attributes from the Properties windows should be set:

  • BuildAction = "Content"
  • Copy to Output Directory = "Do Not Copy"
  • Custom Tool = <clear any existing value>

The XAML parser (XamlReader.Load) does not care about the extension; however, using the .xaml extension will be more intuitive and representative of the content. SLDialogBox is only responsible for showing and closing the dialog. The child implementations will be customized to suit the needs of the application.

Push Notification Implementation

A call center application must be able to do a screen pop with the caller information. A call center workday starts with the rep registering with the call center server. Push notifications are implemented with connection-oriented sockets. The entire call manager server implementation is not shown in the figures but is available in the code download. When the Silverlight client makes a socket connection on the server, a new RepConnection object gets added to the RepList. The RepList is a generic list indexed by a unique Rep Number. When a call comes in, you will use this list to find an available rep and, using the socket connection associated with the RepConnection, notify the agent with the call information. RepConnection uses ReceiveBuffer, as shown in Figure 13.

Figure 13 RepConnection Uses 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; } }

You will use a Silverlight call simulator to drop a call into the CallDispatcher._callQueue to trigger the screen pop process. The CallDispatcher is not shown in any of the figures, but it is available in the downloadable code. It attaches a handler to _callQueue.OnCallReceived and will get notified when the simulator enqueues the message to the _callQueue inside the ProcessMessage implementation. By taking advantage of the pop-up dialogs I discussed previously, the client will display the Accept/Reject notification, as shown in Figure 14. Here's the line of code that is responsible for displaying the actual notification dialog from Figure 8:

ClientGlobals.notifyCallPopup.ShowDialog(true);

Figure 14 Incoming Call Notification

Cross-Domain Access of TCP Services

Unlike the media and the ad display applications, real enterprise-class LOB applications require integration with a variety of service-hosting environments. For instance, the call center application is hosted on a Web site (advcallclientweb hosted at localhost:1041), uses a stateful socket server on another domain (localhost:4230) for screen pop, and reaches LOB data through services hosted on a different domain (localhost:1043). It will use yet another domain for transmitting instrumentation data.

The Silverlight sandbox doesn't, by default, allow network access to any domain other than the domain of origin—advcallclientweb (localhost:1041). When such network access is detected, the Silverlight runtime checks for the opt-in policies established by the destination domain. Here is a typical list of the service-hosting scenarios that need to support cross-domain policy requests by the client:

  • Services hosted in the cloud
  • Web services hosted in a service process
  • Web services hosted in IIS or other Web servers
  • HTTP resources such as XAML markup and XAP packages
  • TCP Services hosted in a service process

While it is straightforward to implement cross-domain policies for HTTP resources and Web service endpoints hosted in IIS, the other cases require the knowledge of the policy request/response semantics. In this section I will briefly implement the policy infrastructure necessary for the TCP screen pop server, which is referred to as Call Manager in Figure 1. The other cross-domain scenarios will be discussed in Part 2 of this article.

Cross-Domain Policies with TCP Services

Any TCP service access in Silverlight is considered a cross-domain request, and the server needs to implement a TCP listener on the same IP address that is bound to port 943. The policy server shown in Figure 3is the listener implemented for this purpose. This server implements a request/response process for streaming out declarative policies needed by the Silverlight runtime before allowing the networking stack on the client to connect to the screen pop server (Call Manager in Figure 3).

For simplicity's sake, I will host the call manager server in a console application. This console application can easily be converted to a Windows service for real implementations. Figure 3shows the typical interaction with a policy server; the Silverlight runtime will connect to the server on port 943 and send a policy request that will contain a single line of text: "<policy-file-request/>".

The XML-based policies enable the scenario shown in Figure 3. The socket resource section can specify a group of ports within the allowed range of 4502 to 4534. The rationale for restricting to a range is to minimize the attack vector, thereby mitigating the risk of accidental weaknesses in the firewall configuration. Since the call center server (Call Manager in Figure 1) listens on port number 4530, the socket resource is configured like so:

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

You can also configure <socket-resource> to allow all the permitted port numbers by specifying port="4502–4534".

To save time, I'll repurpose the code from the call manager server in implementing the policy server. A Silverlight client connects to the policy server, submits a request, and reads the response. Policy server closes the connection once the policy response is sent successfully. The policy contents are read by the policy server from a local file, clientaccesspolicy.xml, which is included in the download.

The TCP listener implementation for policy server is shown in Figure 15. This uses the same async looping pattern discussed earlier for TCP Accept. Clientaccesspolicy.xml is read into a buffer and reused for sending to all the Silverlight clients. The Client­Connection encapsulates the accepted socket and the receive buffer that will be associated with the SocketAsyncEventArgs.

Figure 15 TCP Policy Server Implementation

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(); } }

The code sample shown in Figure 15reuses SocketAsyncEvent­Args between multiple TCP accepts. For this to work, e.AcceptSocket should be set to null in AcceptAsyncCallback. This approach will prevent GC churn on the server with high scalability requirements.

Wrapping Up

Implementation of the push notifications is an important aspect of a call center application and is an enabler of the screen pop process. Silverlight makes the screen pop implementation a lot easier than the AJAX or similar frameworks. Since the server programming and client programming models are similar, I was able to attain some reusability at the source-code level. In the case of the call center, I could use message definitions and receive buffer abstractions in both the server and client-side implementations.

I will implement on-premise Web service integration, security, cloud-service integration, and application partitioning in Part 2 of this series. I hope this effort resonates well with some of the LOB scenarios you come across, and I look forward to your feedback.

I would like to thank Dave Murray and Shane DeSeranno of Microsoft for offering guidance on the internals of Silverlight socket implementation, along with Robert Brooks, a domain expert on call centers for the screen pop discussion.

Hanu Kommalapati is a Platform Strategy Advisor at Microsoft, and in his current role he advises enterprise customers in building scalable line-of-business applications on Silverlight and Azure Services platforms.