Data access in Silverlight applications works differently than it does in traditional applications. You'll need to be aware of how it works and the limitations. In this chapter, you will look at what makes data access different, and then explore mechanisms for accessing data in a Silverlight application.
As discussed in Chapter 1, RIAs bridge the gap between Windows-based smart clients and web-based applications. When moving to this type of environment, data access and networking can be confusing.
In a Windows-based smart client, the application has access to the database at all times. It can create a connection to the database, maintain state with the database, and remain connected.
On the other hand, a web application is what is known as a pseudo-conversational environment, which is, for the most part, a completely stateless and disconnected environment. When a client makes a request to the web server, the web server processes the request and returns a response to the client. After that response has been sent, the connection between the client and the server is disconnected, and the server moves on to the next client request. No connection or state is maintained between the two.
In Silverlight applications, we have one additional layer of complexity. The application runs from the client's machine. However, it is still a disconnected environment, because it is hosted within a web browser. There is no concept of posting back for each request or creating a round-trip to the server for data processing. Therefore, data access is limited to a small number of options.
In addition, a Silverlight application has a number of security restrictions placed on it to protect the users from the application gaining too much control over their machine. For instance, the Silverlight application has access to only an isolated storage space to store its disconnected data. It has no access whatsoever to the client's hard disk outside its "sandbox." Silverlight's isolated storage is discussed in more detail in Chapter 9.
What are your options for accessing data in a Silverlight application? The following main mechanisms are available:
The most common mechanism to access data from a Silverlight application is through web services, typically a WCF service.
Silverlight applications can access data using ADO.NET Data Services, which provides access to data through a URI syntax.
Silverlight also has built-in socket support, which allows applications to connect directly to a server through TCP sockets.
Silverlight has out-of-the-box support for JavaScript Object Notation (JSON), as well as RSS 2.0 and Atom 1.0 syndication feed formats.
Of these mechanisms, I'll explore accessing WCF services from Silverlight in depth, and then have a high-level look at using sockets. For examples and more information on accessing other data services, refer to Pro Silverlight 4 in C# by Matthew MacDonald (Apress, 2010).
One of the ways that a Silverlight application can access data is through web services. These can be ASP.NET Web Services (ASMX), Windows Communication Foundation (WCF) services, or representational state transfer (REST) services. Here, you will concentrate on using a WCF service, which is the preferred way of accessing data in a Silverlight application through web services.
To demonstrate accessing data from a WCF service, you will build the same application that you built in Chapter 5 to try out the DataGrid
. (For more information about any part of this exercise regarding the DataGrid
, refer back to Chapter 5.) The difference will be that the application will get the data through a web service.
As you'll recall, this application displays common starting hands in poker and the nicknames that have been given to those starting hands. The UI will have three columns: the first column will display two images of the cards in the hand, the second column will display the nickname, and the third column will contain notes about the hand. The completed application is shown in Figure 7-1.
Create a new Silverlight application in Visual Studio 2010. Call the application WCFService
, and allow Visual Studio to create a Web Application project named WCFService.Web
to host your application.
Right-click the WCFService.Web
project and select Add
Now you need to implement the StartingHands.cs
class. It is very similar to the class used in Chapter 5's DataGrid
example. To save yourself some typing, you can copy the code from that project. As shown in bold in the following code, the only differences are the namespace and the return type of the GetHands()
method. Instead of using an ObservableCollection
, it will return a simple List<StartingHands>
.
In a real-world example, the StartingHands.cs
class would be doing something like retrieving data from a SQL Server database and executing some business logic rules on the data. For simplicity, this example just returns a static collection.
using System; using System.Collections.Generic; using System.Linq; using System.Web;namespace WCFService.Web
{
public class StartingHands { public string Nickname { get; set; } public string Notes { get; set; } public string Card1 { get; set; } public string Card2 { get; set; }public static List<StartingHands> GetHands()
{List<StartingHands> hands = new List<StartingHands>();
hands.Add( new StartingHands() { Nickname = "Big Slick", Notes = "Also referred to as Anna Kournikova.", Card1 = "As", Card2 = "Ks" }); hands.Add( new StartingHands() { Nickname = "Pocket Rockets", Notes = "Also referred to as Bullets.", Card1 = "As", Card2 = "Ad" });
hands.Add( new StartingHands() { Nickname = "Blackjack", Notes = "The casino game blackjack.", Card1 = "As", Card2 = "Js" }); hands.Add( new StartingHands() { Nickname = "Cowboys", Notes = "Also referred to as King Kong", Card1 = "Ks", Card2 = "Kd" }); hands.Add( new StartingHands() { Nickname = "Doyle Brunson", Notes = "Named after poker great Doyle Brunson", Card1 = "Ts", Card2 = "2s" }); return hands; } } }
Next, you need to add the WCF service that will call the StartingHands.GetHands()
method. Right-click the WCFService.Web
project and select Add
This will add a service named StartingHandService.svc
to the project with an attached code-behind file named StartingHandService.svc.cs
. View that code behind. You will see that Visual Studio has already created the base WCF service, including a sample method called DoWork()
, as follows:
namespace WCFService.Web { [ServiceContract(Namespace = "")] [AspNetCompatibilityRequirements( RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)] public class StartingHandService { [OperationContract] public void DoWork() { // Add your operation implementation here return; } // Add more operations here and mark them with [OperationContract] } }
Replace the DoWork()
method with a GetHands()
method that returns a List<StartingHands>
collection, as follows:
namespace WCFService.Web { [ServiceContract(Namespace = "")] [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)] public class StartingHandService { [OperationContract]public List<StartingHands> GetHands() {
return StartingHands.GetHands();}
// Add more operations here and mark them // with [OperationContract] } }
This method simply returns the results from calling the StartingHands.GetHands()
method. Note that you will need to add a using statement for System.Collections.Generic
.
Now that you have a Silverlight-enabled WCF service, you need to add a reference in your Silverlight project so that your Silverlight application can access the service. To do this, right-click References within the WCFService
in Solution Explorer and select Add Service Reference, as shown in Figure 7-4. This brings up the Add Service Reference dialog box.
In the Add Service Reference dialog box, click the Discover button, as shown in Figure 7-5.
Visual Studio will find the StartingHandService.svc
and will populate the Services list in the Add Service Reference dialog box. Note that you may need to build the solution before Visual Studio will find your service. Expand the StartingHandService.svc
node to show the StartingHandService
. Click StartingHandService
to see the GetHands()
web method in the Operations listing, as shown in Figure 7-6. Enter StartingHandServiceReference
as the Namespace field, and then click OK to continue.
Open the Visual Studio Object Browser by selecting View
Look at the members listed on the right side of the Object Browser. There are a number of items that are added, but take specific note of the method named GetHandsAsync()
and the event named G
etHandsCompleted
. You will need to use bothof these in order to call your web service from Silverlight.
Now it's time to create the Silverlight application's UI. Open the Main
Page.xaml
file in Visual Studio. Place the cursor within the root Grid
and double-click the DataGrid
control in the Toolbox. Once the DataGrid has been added, right-click on it in designer and select Reset Layout
<Grid x:Name="LayoutRoot" Background="White">
<sdk:DataGrid AutoGenerateColumns="False" Name="dataGrid1" />
</Grid>
Rename the DataGrid
to grdData
and set the Margin to 15. Next, add the following Column definitions, which are from the previous DataGrid
exercise in Chapter 5. The DataGrid
contains three columns: one template column containing the two cards in the hand and two text columns containing the nickname and notes about the hand.
<sdk:DataGrid AutoGenerateColumns="False" Name="grdData" Margin="15"><sdk:DataGrid.Columns>
<sdk:DataGridTemplateColumn Header="Hand">
<sdk:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Border
Margin="2" CornerRadius="4"
BorderBrush="Black"
BorderThickness="1" />
<Rectangle
Margin="4" Fill="White"
Grid.Column="0" />
<Border
Margin="2" CornerRadius="4"
BorderBrush="Black"
BorderThickness="1"
Grid.Column="1" />
<Rectangle
Margin="4" Fill="White"
Grid.Column="1" />
<TextBlock
Text="{Binding Card1}"
HorizontalAlignment="Center"
VerticalAlignment="Center" "
Grid.Column="0" />
<TextBlock
Text="{Binding Card2}"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Grid.Column="1" />
</Grid>
</DataTemplate>
</sdk:DataGridTemplateColumn.CellTemplate>
</sdk:DataGridTemplateColumn>
<sdk:DataGridTextColumn
Header="Nickname"
Binding="{Binding Nickname}" />
<sdk:DataGridTextColumn
Header="Notes"
Binding="{Binding Notes}" />
</sdk:DataGrid.Columns>
</sdk:DataGrid>
Save the MainP
age.xaml
file and navigate to the code behind for the application, located in the MainPage
.xaml.cs
file. Wire up the Loaded
event handler for the page, as follows:
public partial class MainPage : UserControl { public MainPage() { InitializeComponent();this.Loaded += new RoutedEventHandler(Page_Loaded);
}void Page_Loaded(object sender, RoutedEventArgs e)
{
throw new NotImplementedException();
}
}
Next, you need to call the WCF service. In Silverlight, web services can be called only asynchronously, so the browser's execution is not blocked by the transaction. In order to do this, you need to get an instance of the service reference (commonly referred to as the web service proxy class) named StartingHandService
, which you added earlier. You will then wire up an event handler for the service's G
etHandsCompleted
event, which you examined in the Object Browser (in step 11). This is the event handler that will be called when the service has completed execution. Finally, you will execute the GetHandsAsync()
method.
In a real-world scenario, you will want to present the user with a progress bar or animation while the service is being called, since the duration of a web service call can be lengthy.
Within the Page_Loaded
event handler, first obtain an instance of StartingHandService
. Then, in the GetHandsCompleted
event handler, bind the ItemsSource
of the DataGrid
to the result returned from the service call, as shown in the following code. Note that normally you will want to check the result to make certain that the web service call was successful, and alert the user accordingly in case of failure.
using WCFService.StartingHandServiceReference;
...
public partial class MainPage : UserControl { public MainPage() { InitializeComponent(); this.Loaded += new RoutedEventHandler(Page_Loaded); } void Page_Loaded(object sender, RoutedEventArgs e) {StartingHandServiceClient service = new StartingHandServiceClient();
service.GetHandsCompleted += new
EventHandler<GetHandsCompletedEventArgs>(
service_GetHandsCompleted);
service.GetHandsAsync();
}void service_GetHandsCompleted(object sender, GetHandsCompletedEventArgs e)
{
this.grdData.ItemsSource = e.Result;
}
}
Test your application. If all goes well, you should see the populated DataGrid
, as shown earlier in Figure 7-1.
This example demonstrated how to use the Silverlight-enabled WCF service provided in Visual Studio to allow your Silverlight application to access data remotely. As noted earlier in chapter in the section "Data Access in Silverlight Applications", this is one of the most common approaches to data access with Silverlight.
In the previous example, the web service was on the same domain as your Silverlight application. What if you want to call a service that is on a different domain?
In fact, as a best practice, it is preferred to have your web services stored on a domain separate from your web application. Even for applications where you control both the web service and the Silverlight application, you may be dealing with different domains.
If you attempt to access a service from a different domain in Silverlight, you will notice that it fails. This is because, by default, a Silverlight application cannot call services that are on a different domain, unless it is permitted to do so by the service host. In order for Silverlight to determine if it has permission to access a service on a certain domain, it will look for one of two files in the root of the target domain: clientaccesspolicy.xml
or crossdomain.xml
.
First, Silverlight will look for a file named clientaccesspolicy.xml
in the domain's root. This is Silverlight's client-access policy file. If you are publishing your own services that you want to be accessible by Silverlight applications, this is the file that you want to use, as it provides the most options for Silverlight application policy permissions. The following is a sample clientaccesspolicy.xml
file:
<?xml version="1.0" encoding="utf-8"?> <access-policy> <cross-domain-access> <policy> <allow-from http-request-headers="*"> <domain uri="*"/> </allow-from> <grant-to> <resource path="/" include-subpaths="true"/> </grant-to> </policy> </cross-domain-access> </access-policy>
The important elements are <allow-from>
and <grant-to>
. The <allow-from>
element defines which domains are permitted to access the resources specified in the <grant-to>
element.
If Silverlight cannot find a clientaccesspolicy.xml
file at the root of the domain from which you are attempting to access a service, it will then look for a file named crossdomain.xml
in the root. This is the XML policy file that has been used to provide access for Flash applications to access cross-domain services, and Silverlight supports this file as well. The following is an example of a crossdomain.xml
file:
<?xml version="1.0"?> <!DOCTYPE cross-domain-policy SYSTEM "http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd"> <cross-domain-policy> <allow-http-request-headers-from domain="*" headers="*"/> </cross-domain-policy>
Again, even though Silverlight supports crossdomain.xml
, using clientaccesspolicy.xml
for Silverlight applications is the preferred and best practice.
In the majority of cases, your Silverlight applications will access data through web services. However, Silverlight provides another mechanism that, though rarely used, can be quite powerful. This mechanism is socket communications. In this section, you will look at a greatly simplified example of communicating with a server via sockets and TCP. The main purpose here is to give you a taste of using sockets in Silverlight so you have a basic understanding of the process and can consider whether you would like to take this approach. If so, you can refer to a more advanced resource, such as Pro Silverlight 4 in C# by Matthew MacDonald (Apress, 2010).
For this example, let's assume that you have a socket server running at the IP address 192.168.1.100 on port 4500. The socket server simply accepts text inputs and does something with them. In Silverlight, you want to connect to that socket server and send it text from a TextBox
control.
First, you make a connection to the socket server. To do this, you create an instance of a System.Net.Sockets.Socket
object for IP version 4 (AddressFamily.InterNetwork
). The type will be Stream
, meaning it will accept a stream of bytes, and the protocol will be TCP.
Socket socket; socket = new Socket( AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
You need to execute the socket's ConnectAsync()
method, but first you must create an instance of SocketAsyncEventArgs
to pass to the method, using a statement similar to the following:
SocketAsyncEventArgs socketArgs = new SocketAsyncEventArgs() { RemoteEndPoint = new IPEndPoint( IPAddress.Parse("192.168.1.100"), 4500) };
This statement sets the target for the socket connection as 192.168.1.100 on port 4500.
In addition, since this is an asynchronous connection, you need to have notification when the connection has been established. To get this notification, you wire up an event handler to be triggered on the SocketAsyncEventArgs.Completed
event. Once you have that wired up, you simply call the ConnectAsync()
method, passing it your SocketAsyncEventArgs
instance.
socketArgs.Completed += new EventHandler<SocketAsyncEventArgs>(socketArgs_Completed); socket.ConnectAsync(socketArgs);
The method for this event handler will first remove the event handler, and then it will examine the response from the socket server. If it is successful, it will send a stream of bytes from your TextBox
control to the socket server through your established connection.
void socketArgs_Completed(object sender, SocketAsyncEventArgs e) { e.Completed -= socketArgs_Completed; if (e.SocketError == SocketError.Success) { SocketAsyncEventArgs args = new SocketAsyncEventArgs(); args.SetBuffer(bytes, 0, bytes.Length); args.Completed += new EventHandler<SocketAsyncEventArgs>(OnSendCompleted); socket.SendAsync(args); } }
Once again, since the calls to the socket are asynchronous, you wire up another event handler called OnSendCompleted
, which will fire when your SendAsync()
method is completed. This event handler will do nothing more than close the socket.
void OnSendCompleted(object sender, SocketAsyncEventArgs e) { socket.Close(); }
Although this seems pretty simple, it is complicated by client-access policy permissions. In the same way that a Silverlight application can call a web service on a separate domain only if it has the proper client-access policy permissions, a Silverlight application can call a socket server only if that server contains the proper client-access policy permissions. The following is an example of a client-access policy for a socket server:
<?xml version="1.0" encoding ="utf-8"?> <access-policy> <cross-domain-access> <policy> <allow-from> <domain uri="*" /> </allow-from> <grant-to> <socket-resource port="4500-4550" protocol="tcp" /> </grant-to> </policy> </cross-domain-access> </access-policy>
Recall that when you're using a web service, the client-access policy is contained in afile named clientaccesspolicy.xml
, which is placed in the domain's root. In a socket access situation, things are a bit more complex.
Before Silverlight will make a socket request to a server on whatever port is requested by the application, it will first make a socket request of its own to the server on port 943, requesting a policy file. Therefore, your server must have a socket service set up to listen to requests on port 943 and serve up the contents of the client-access policy in order for Silverlight applications to be able to make a socket connection.
In this chapter, you focused on accessing data from your Silverlight applications through WCF services. I also discussed accessing data from different domains and cross-domain policy files. In addition, you looked at using sockets in Silverlight from a high level.
In the next chapter, you will look at Silverlight's Navigational Framework.