August 2009
Volume 24 Number 08
Data Points - Data Performance and Fault Strategies in Silverlight 3
By John Papa | August 2009
This article is based on prerelease versions of Silverlight 3.
Contents
Built for Speed
Configuring Binary Encoding
Binary Data Versus Text Data
Error Messages Are Data
Tackling the Issues
Undeclared Faults
Declared Faults
Wrapping Up
Silverlight applications often rely on Web services for their data. The performance of data retrieval and the ability to retrieve meaningful information about exceptions that may occur in Web services are two critical areas that have been improved in Silverlight 3.
Poor performance can be an application killer. Good strategies for retrieving data from a Web service can help, but sometimes it is necessary to retrieve an object graph that can be huge and take a long time to pass from a Web service to a client. Silverlight 3 offers a new feature that passes data from a Web service using binary encoding, and this can dramatically improve performance when passing large object graphs.
A lot can go wrong when passing data between services and Silverlight applications. That is why it is important to have a good strategy for handling exceptions that may occur when calling a Web service. Silverlight 3 offers some networking enhancements that give developers more options to pass information about managed exceptions from Web services.
In this month's column, I will demonstrate how binary encoding works, the effect it has on an application's performance, and how it behaves by demonstrating it in action. I will also walk through several techniques that can be used to pass exception information using undeclared and declared faults from Windows Communication Foundation (WCF) Web services to Silverlight. I will start by demonstrating what happens when an exception occurs and how to add some quick changes to the configuration to show information while debugging. Then, I will show you how to set up a strategic fault pattern to handle the passing exception information over SOAP services, using declared faults. All code is based on the Silverlight 3 beta and accompanies this article online.
Built for Speed
SOAP and XML passed as text severely bloats the message being passed between WCF and Silverlight. This can have a negative effect on performance, in both processing the data and the time it takes to pass the data over HTTP. Silverlight 3 introduces the ability to use a binary message encoder with WCF services that communicate with Silverlight 3 client applications. The binary message encoder can improve the performance of WCF services, especially when passing large objects graphs. The biggest gains in performance using binary message encoding are realized when passing arrays, numbers, and object graphs; lesser gains are found with very small messages and strings.
This is not a compression strategy, so there is no negative effect on performance for packing and unpacking compressed data. However, the binary encoding usually does reduce the size of the data being passed. Size reduction is not guaranteed, but is readily apparent when using large object graphs and integer data. The key improvement gained from binary encoding is that it is optimized to increase server throughput.
Configuring Binary Encoding
WCF services can communicate with Silverlight 2 applications using basicHttpBinding, which sends data as text over HTTP. When using the Silverlight-enabled WCF Service file template—which is installed when you install the Silverlight tools for Visual Studio—to create a WCF service for Silverlight 2, the binding was configured to use basicHttpBinding. This file template has been changed in Silverlight 3 to configure the WCF service to use the binary message encoder instead of text.
The Silverlight-enabled WCF Service file template configured a WCF service to use binary-encoded messaging. If you use an existing WCF service, it can be configured to use binary message encoding by creating a custom binding in the bindings section of the Web.config file. The following code sample shows the custom binding, named silverlightCustomBinding, as it appears in the <system.serviceModel> section of a configuration file. The silverlightCustomBinding, configured to use binaryMessageEncoding, is then referenced by its name in the service's endpoint configuration.
<endpoint address="" binding="silverlightCustomBinding" contract="MyTestService" /> <bindings> <customBinding> <binding name="silverlightBinaryBinding"> <binaryMessageEncoding /> <httpTransport /> </binding> </customBinding> </bindings>
Since basicHttpBinding sends messages as text over HTTP, it is easy to debug the messages through a tool such as Fiddler. While basicHttpBinding can still be configured, the advantages of the binary encoder can be so great that it is the recommended approach. It is easy to toggle back and forth between binary and text, simply by changing the config file. This is convenient when debugging a WCF service. Binary encoding is only supported by WCF services and clients. If you need a non-WCF client application to consume your WCF service, it is best not to use binary encoding.
Binary Data Versus Text Data
The first demonstration will show the differences, in both configuration and performance, between using basicHttpBinding and a binary message encoding between WCF and Silverlight 3. The sample application included with this article breaks both examples (text and binary) out into separate services that can be called from the same Silverlight 3 client.
The configuration for these services in the Web.config file of the sample application is shown in Figure 1. The differences between the text and binary encoding configurations are in bold. Notice that the service SpeedService0 uses the basicHttpBinding, while SpeedService1 uses a customBinding with the binding configuration named silverlightBinaryBinding (shown in the previous code sample.)
Figure 1 Configuring Text vs. Binary
<services> <service behaviorConfiguration="SilverlightFaultData.Web.SpeedServiceBehavior" name="SilverlightFaultData.Web.SpeedService0"> <endpoint address="" binding="basicHttpBinding" contract="SilverlightFaultData.Web.SpeedService0" /> <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" /> </service> <service behaviorConfiguration="SilverlightFaultData.Web.SpeedServiceBehavior" name="SilverlightFaultData.Web.SpeedService1"> <endpoint address="" binding="customBinding" bindingConfiguration="silverlightBinaryBinding" contract="SilverlightFaultData.Web.SpeedService1" /> <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" /> </service> </services>
The services SpeedService0 and SpeedService1 both retrieve all products and each product's category, supplier, and order details. The query (shown in Figure 2) uses the Entity Framework to retrieve the object graph.
Figure 2 Retrieving an Object Graph
[OperationContract] public IList<Products> DoWork() { var ctx = new NorthwindEFEntities(); var query = from p in ctx.Products .Include("Categories") .Include("Suppliers") .Include("OrderDetails") select p; var productList = query.ToList<Products>(); return productList; }
One of the best aspects of the binary message encoding is that the only changes are found in the configuration file. No changes need be made to the code.
When the sample application is run and the Text encoding option is selected (as shown in Figure 3), the service that uses basicHttpBinding is executed. The object graph is returned and using an HTTP monitoring tool such as Fiddler or FireBug, the results show that the object graph in text form was 4MB in size and took 850ms to retrieve. When choosing the Binary encoding option, the object graph returned is 3MB and took 600ms to retrieve. While this is a small sample using a moderately sized object graph from the Northwind database, the results are in line with the Silverlight Web Service team's benchmarks. In this sample using the binary encoding, the object graph contains about 2,300 total objects and is reduced in size by 25% and is 30% faster than the text encoding.
Figure 3 Getting the Data via BasicHttpBinding
Error Messages Are Data
When .NET managed exceptions are thrown in a Web service, they cannot be converted to a SOAP message and passed back to a Silverlight 2 client application. Also, Silverlight 2 cannot read SOAP faults. These two issues make debugging Web services difficult with Silverlight 2. Because SOAP Faults cannot be used with Silverlight 2, a common error message that most Silverlight 2 developers eventually run into when accessing a Web service is the infamous "The remote server returned an error: NotFound," which contains no pratical information. The original exception and its details are not transported to the Silverlight 2 client, which makes debugging the Web services difficult. Error messages contain data that is often critical in determining how the client application should respond. For example, Figure 4shows the results of calling a Web service where an exception is thrown because the database cannot be found.
Figure 4 Infamous NotFound Error
When the exception is raised, an HTTP status code of 500 is returned to Silverlight. The browser networking stack prevents Silverlight from reading responses with a status code of 500, so any SOAP fault information contained within is unavailable to the Silverlight client application. Even if the message could be retrieved, Silverlight 2 is not capable of converting the fault back into a managed exception. Both of these issues have been addressed in Silverlight 3.
Tackling the Issues
Handling exceptions with WCF and Silverlight 3 requires tackling both of these issues. First, for the exception to be returned to the Silverlight client without the networking browser stack preventing Silverlight from reading it, the status code must be changed from 500 to something that allows Silverlight to read the response. This can be achieved by deriving from the BehaviorExtensionElement and implementing IEndpointBehavior class, making it change the status code from 500 to 200 prior to whenever a fault occurs, and setting the services to use the behavior in the configuration file. The MSDN documentation contains a WCF endpoint behaviorthat can be used to accomplish this, and thus allow Silverlight clients access to the contents of the fault. The following code sample shows the specific code in the SilverlightFaultBehavior class that converts the status code:
public void BeforeSendReply(ref Message reply, object correlationState) { if (reply.IsFault) { HttpResponseMessageProperty property = new HttpResponseMessageProperty(); // Here the response code is changed to 200. property.StatusCode = System.Net.HttpStatusCode.OK; reply.Properties[HttpResponseMessageProperty.Name] = property; } }
The SilverlightFaultBehavior class can be referenced in the Web.config file as a behavior extension, as shown in the following code snippet:
<extensions> <behaviorExtensions> <add name="silverlightFaults" type="SilverlightFaultBehavior.SilverlightFaultBehavior, SilverlightFaultBehavior, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" /> </behaviorExtensions> </extensions>
With the SilverlightFaultBehavior in place, the second issue is getting Silverlight to be able to convert the fault to a managed exception, so it can read it. Though this is not possible with Silverlight 2, Silverlight 3 now has the ability to process faults. This allows Silverlight 3 to read a fault and present appropriate information to the user when an exception is thrown in a Web service.
The entire process of reading the exception information in Silverlight 3 goes something like this:
1) An exception is thrown in a Web service.
2) The service uses the SilverlightFaultBehavior to convert the HTTP status code from 500 to 200.
3 )The exception is converted to a SOAP fault and passed to the Silverlight 3 client.
4) The browser allows Silverlight to read the message because it has a status code of 200.
5) Code in the Silverlight 3 application checks the type of error to see if it is a FaultException or a FaultException<ExceptionDetail>.
Undeclared Faults
SOAP-based WCF services communicate errors using SOAP fault messages, or .NET managed exceptions. Therefore, the .NET managed exceptions are converted to a SOAP fault, passed to the client, and then translated back to a .NET managed exception.
Faults can be undeclared or declared, and all are strongly typed. Undeclared faults are not specified in the operation contract and should only be used for debugging. Undeclared faults return the exception message to the client exactly as it was raised in the Web service. To allow an undeclared fault, the config element's includeExceptionDetailInFaults attribute must be set to true, as shown below:
<serviceDebug> <behavior name="SilverlightFaultData.Web.Service1Behavior"> <serviceMetadata httpGetEnabled="true" /> <serviceDebug includeExceptionDetailInFaults="true" /> </behavior>
The sample application's Service1 uses this behavior, which then allows the exception to be converted automatically into a FaultException<ExceptionDetail>. The Silverlight 3 client can then check the e.Error argument in its asynchronous completion event handler and take appropriate action, as shown in the code below and in Figure 5:
if (e.Error != null) { ErrorPanel.DataContext = e.Error; if (e.Error is FaultException<ExceptionDetail>) { var fault = e.Error as FaultException<ExceptionDetail>; ErrorPanel.DataContext = fault; } }
Figure 5 Undeclared FaultException
Undeclared faults show the exception in its raw state with all of the ugly error information, which is obviously not a good idea to show to a user. For this reason, it is not recommended to use undeclared faults in a production application. The managed exceptions can contain internal application information too (sometimes sensitive information). Setting the includeExceptionDetailInFaults to true should only be done when temporarily debugging an application error, and not in production environments. I strongly recommend that the includeExceptionDetailInFaults is set to false for production applications.
Declared Faults
A declared fault is created when the service operation is decorated with a FaultContractAttribute (or a derived type of the FaultContractAttribute). Unlike undeclared faults, declared faults are good for production as they specifically translate an exception's information in code to the fault type. In the Web service, a developer can create the fault type in code and set its properties with information that is appropriate to send to Silverlight. The fault should only be filled with information that the client must know. Any sensitive information (such as credentials) should not be sent to the client in the fault. In the Silverlight client, a developer can write code to look for that type and tell the user something appropriate.
Figure 6shows the service operation being decorated with the FaultContract attribute with a type of DataFault. The general FaultContract<typeof(ExceptionDetail)> could have been used, though I recommend using a specific custom fault type for the operation. In this case, the operation uses the DataFault type that I created in the sample application. This service operation will fail because the database cannot be found. An exception will be thrown and then caught by the try/catch block, where the exception is read and key information is put into the DataFault before it is thrown. At this point, the DataFault is converted to a SOAP fault and sent back to the Silverlight client with a status code of 200.
Figure 6 Creating a Declared Fault
[OperationContract] [FaultContract(typeof(DataFault))] public IList<Products> DoWork() { try { var ctx = new NorthwindEFEntities(); var query = from p in ctx.Products select p; return query.ToList<Products>(); } catch (Exception ex) { DataFault fault = new DataFault { Operation = Operation.Other, Description = ex.Message }; throw new FaultException<DataFault>(fault, "because"); } }
The DataFault class (shown in Figure 7) defines an Operation property and a Description property. The properties that the fault contains are up to the developer. The properties should represent the key information for the fault so it can be examined by the Silverlight client. The operation is set to a custom enumeration of type Operation (also shown in Figure 7) that will indicate the type of SQL operation that was being performed when the exception occurred. The Description should be set to a custom message and not to the exception message to avoid sending any sensitive information. (The sample application uses ex.Message just for demonstration purposes. I do not recommend passing the exception's Message directly back to the Silverlight client.) The FaultException also accepts a parameter that represents the reason for the exception. In the sample, the reason is set to "because." The reason can be used to help the client classify the cause of the exception.
Figure 7 DataFault class
public class DataFault { public Operation Operation { get; set; } public string Description { get; set; } } public enum Operation { Select, Insert, Update, Delete, Other }
The sample's Service3 has a configuration whose endpoint indicates that the behaviorConfiguration should use the SilverlightFaultBehavior class (this translates the status code from 500 to 200). The configuration is shown here:
<service behaviorConfiguration="SilverlightFaultData.Web.Service3Behavior" name="SilverlightFaultData.Web.Service3"> <endpoint address="" behaviorConfiguration="SilverlightFaultBehavior" binding="customBinding" bindingConfiguration="silverlightBinaryBinding" contract="SilverlightFaultData.Web.Service3" /> <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" /> </service>
When the service that uses the declared fault is executed, the Silverlight client receives and can read the fault. The following code is executed when the asynchronous Web service operation completes:
if (e.Error is FaultException<ServiceReference3.DataFault>) { var fault = e.Error as FaultException<ServiceReference3.DataFault>; ErrorPanel.DataContext = fault; }
The Error is checked to see if it is a FaultException of type DataFault. If it is, then its individual properties Operation and Description can be examined. Figure 8shows the DataFault's custom information displayed directly to the user.
Figure 8 Examining the Declared Fault
In production applications, a custom fault strategy should be devised to map some exceptions to SOAP faults. The key here is determining the circumstances under which exceptions should be mapped to faults. This depends on whether the client application should be informed of specific information about errors on the server.
Wrapping Up
This article explained how Silverlight 3 applications can benefit from both binary encoding and exception management features. Binary message encoding is a solid choice over basicHttpBinding when using .NET WCF clients, such as Silverlight. Exceptions often contain critical information that can help in debugging an application. This article showed how to surface exception information in both development and production environments, using the Silverlight 3 enhancements.
Send your questions and comments for John to mmdata@microsoft.com".
John Papa( johnpapa.net) is a senior consultant and a baseball fan who spends summer nights rooting for the Yankees with his family. John, a Silverlight MVP, Silverlight Insider, and INETA speaker, has authored several books, including his latest, titled Data-Driven Services with Silverlight 2(O'Reilly, 2009). He often speaks at conferences such as VSLive!, DevConnections, and MIX.