Silverlight Best Practices: Data-Centric Applications
Almost all applications are data-driven. Data is produced by various sources and consumed by applications that translate the data into meaningful information. Silverlight is a client technology, and data is often produced at the server, so it must be transported to the Silverlight client. Unlike content-driven applications that focus on documents and process-driven applications that are based on workflows and algorithms, data-centric applications are built around the underlying data and expose common operations known as the CRUD operations (Create, Read, Update, and Delete).
There are three primary concerns to address within a data-centric application built with Silverlight. The first is the serialization strategy, or how the data is packaged for transport between the client and server. The second concern is the transport itself and how the server and client keep the data synchronized. The final concern is the presentation of the data to allow user interaction, whether through visualization of the data or the ability to edit the data with validations.
Serialization Strategies
Data can pass through many forms in the course of traveling from a backend system to the end user. On the server, data might be represented by a dictionary of names and values. It may also be encapsulated in a class and combined with behaviors. When data is sent from the server to the client, it must be translated into a format that can be transmitted over the network and consistently reassembled on the client-side. The easiest way to demonstrate this is to build a simple class that contains some data and a simple behavior (combining the first and last names to provide a full name):
public class Contact { public int Id { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public string FullName { get { return string.Format("{0} {1}", FirstName, LastName).Trim(); } } }
If you add a default Silverlight-enabled Windows Communication Foundation (WCF) web service that returns a contact and invoke the service from Silverlight, the actual data sent over the network looks like this:
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"> <s:Body> <GetContactResponse xmlns="http://www.jeremylikness.com/examples/"> <GetContactResult xmlns:a="http://schemas.datacontract.org/2004/07/DataCentricApplications.Model" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"> <a:FirstName>Jeremy</a:FirstName> <a:Id>1</a:Id> <a:LastName>Likness</a:LastName> </GetContactResult> </GetContactResponse> </s:Body> </s:Envelope>
This is what is referred to as SOAP, or Simple Object Access Protocol. If you’ve had to work with complicated SOAP interfaces, you know there is nothing simple about SOAP. While the underlying technology is XML, it takes some work to get to the actual information. Also notice that the behavior for the full name is not transmitted; the client must either reconstruct this, or share a common project with the server so the type that the response is mapped to can retain the desired behavior. Fortunately, Silverlight supports SOAP out of the box, and can easily connect to a SOAP service and create the appropriate proxy to interact with the interface.
A more simplified version of the API might be implemented with a handler that serializes the object directly. A handler that uses the XmlSerializer will generate the following:
<Contact xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <Id>1</Id> <FirstName>Jeremy</FirstName> <LastName>Likness</LastName> </Contact>
As you can see, the format is far more compact. This is referred to as POX for Plain Old XML. There is another format popular on the web called JSON, for JavaScript Object Notation. This format is more readable and compact than XML. It is also generated in a format that makes it easy for JavaScript to create an object from the representation (it can be directly evaluated). Silverlight can use the System.Json assembly to de-serialize JSON instances. The same contact record in JSON looks like this:
{"Id":1,"FirstName":"Jeremy","LastName":"Likness", "FullName":"Jeremy Likness"}
The most compact way to serialize data is through binary serialization. While .NET contains a BinaryFormatter class for automatically serializing objects, Silverlight does not have the corresponding type. It is necessary to manually serialize the objects using a binary writer that produces a byte array, then reverse the process by using a binary reader on the Silverlight end. You can also use a third-party solution for packaging the content.
When you consume a third-party API, chances are you are not in control of the serialization strategy and must use one compatible with the existing API. On the other hand, if you are building your own API, there are several factors to consider:
- How large is the data? Larger data sets can expand exponentially when they must be serialized to XML. The binary approach is preferred in this case.
- How critical is preserving bandwidth? If you are writing an API that will be invoked by a smartphone, you’ll want to minimize the amount of cellular bandwidth that the messages consume-again, consider something like binary over JSON and XML.
- How important is it to adhere to open standards? If you want the API to be accessible by as many clients as possible, it makes sense to use a standard like SOAP, POX, or JSON to format the data, as there are many clients that recognize data in that format.
Often choosing the right strategy is a balance. For example, you may decide to use JSON because it is more compact than XML but provides a more open standard than binary.
Transport Strategies
Serializing the type is only part of the strategy. The type must be transported. All of the above examples use the Hypertext Transfer Protocol (HTTP) to transfer the payloads. There are more complex protocols layered on top of HTTP such as SOAP. If you wanted to secure the transfer, you could use HTTPS to provide a secured sockets layer (SSL) over the HTTP channel. Representational State Transfer, or REST, is a simple protocol for sharing data that can also be used in conjunction with HTTP. Unlike SOAP, which is considered a remote procedure call (RPC) protocol, REST is a resource-based protocol. The API is based on well-defined locations that contain resources, rather than procedure calls with method signatures.
For high-performance applications that move a lot of real-time data, you may want to go to a lower level than the HTTP protocol and communicate directly over sockets. Sockets are accessible through the System.Net.Sockets namespace. Sockets provide real-time duplex communication using either TCP (Transmission Control Protocol) or UDP (Universal Datagram Protocol). Like other communication methods in Silverlight, socket communication is asynchronous. The lifecycle of a socket starts with a connection, continues through a series of asynchronous send and receive operations, and concludes when the socket is shut down. For more information on programming with sockets in Silverlight, read the MSDN reference Working with Sockets.
There are other services that are layered on top of existing protocols to facilitate ease of communication between the client and the server. A popular standard for exposing data on networks is called the Open Data Protocol (OData). The protocol provides a standard for exposing data as resources on the web using an HTTP-based REST interface. Visual Studio provides an item called WCF Data Services for exposing existing data models using OData. You can read a walkthrough of this in the MSDN reference How to: Create the Northwind Data Service.
The service that is exposed can be consumed as a service reference in the Silverlight client. The client will query the service and generate the necessary classes and operations to parse, query, and update the service. It is even possible to manipulate binary data as a stream. To learn more about using Silverlight to consume OData, read the MSDN reference WCF Data Services Client Library for Silverlight.
Another popular way to share data in Silverlight is by using WCF RIA Services. WCF RIA is significant because it allows rapid development across common backend data systems and integrates directly with Entity Framework. WCF RIA allows you to use data annotations to set validations and uses a technique called projection to share code between the client and the server. Your code on the client is written as if the data connection were local, and WCF RIA handles the underlying tasks of wiring up web services and marshaling calls between the client and the server.
Visualization Strategies
Silverlight provides a number of controls out of the box to help interact with data. These controls can automatically recognize data types, generate columns and rows, and even provide CRUD functionality out of the box. They are capable of working with data annotations to provide extended descriptions as well as performing validations. Using these controls in data-centric applications can significantly accelerate the development lifecycle.
DataGrid
The DataGrid control provides a grid-style view of data. The items source of the control can be bound to any class that implements IEnumerable, such as lists and observable collections. For the grid to recognize inserts and updates, the collection should implement the ICollectionChanged interface, and individual elements should implement INotifyPropertyChanged. The column generation recognizes data annotations so user-friendly names can be displayed in the header using the DisplayAttribute. If the source data implements ICollectionView, the grid will also provide grouping, sorting, and filtering.
The grid will support paging if the source collection implements IPagedCollectionView. An easy way to add paging is to use the PagedCollectionView to wrap the source data. You can then set that as the source for the grid and add a DataPager control to display the paging controls. The pager control supports several different styles of paging. Refer to the MSDN Documentation to see what the different options look like. All options can be further styled using the parts and templates system.
The most significant functionality of the data grid is that it will allow inline editing of records within the grid if the underlying objects implement IEditableObject. This is automatically implemented when you use WCF RIA. The edits support full validation using any data annotations set for the properties on the target object.
DataForm
The data form is similar to the data grid, but it works with a single record at a time. It allows you to bind to a collection of items and will provide the controls for navigating between items to edit one at a time. It supports all CRUD modes, and like the data grid will use data annotations to display labels for fields. It will also recognize the DescriptionAttribute and provide an icon with a tooltip to provide the user with additional information about the item. The data form is advanced enough to recognize underlying data types and expose Boolean values as checkboxes, provide calendar pickers for date fields, and provide a combo-box when a data relationship exists.
The data form can be used in conjunction with the data grid. It is a common pattern to use the data grid to display the list of items and expand the row to a detail view when the user selects it. The expanded selection then uses the data form to provide a detailed surface for editing the data.
ValidationSummary
The validation summary object works with the Silverlight validation system to provide a summary of validation issues detected on the page. The data form integrates the validation summary by default, but it can be added to your own custom pages as well. By default it provides a stacked list of validation details for items that failed validation on the current form, but it can be completely styled to fit your custom look and feel.
Summary
The ability of Silverlight to interface with multiple protocols and methods for serializing data makes it ideal for building line-of-business applications that interact with both internal and external web-based services. The addition of features like WCF RIA makes it a viable platform for rapid development of data-centric applications. Using a combination of WCF RIA and the DataGrid and DataForm controls, it is possible to build a fully functional CRUD application that supports full editing and validation in well under an hour. For customized applications and workflows, you can easily connect to legacy systems and services to marshal data between the client and the server.