Sep 30, 2008

Faults and Exceptions when using Web Services in Silverlight 2

I often come across the following questions about web service usage in Silverlight:

- Why are exceptions not "propagated" from WCF services to Silverlight clients?

- Why are SOAP Faults not supported?

- Given the limitations on exceptions/faults, are there any tricks I can use to make my scenario work? Is anything planned to improve this in the future?

- I can't even get basic error handling to work when calling web services in Silverlight. Can I at least tell when something went wrong, even if I can't access the exact fault details?



I'll tackle these one by one:



WCF Exceptions

Two things stop exceptions from propagating from a WCF service to a Silverlight client.



The first one is easy to fix: WCF normally hides exception details from the client, for security reasons, but it is possible to change this during debugging by changing the IncludeExceptionDetailInFaults setting as described in http://blogs.devdeo.com/carlos.medina/PermaLink,guid,b3bff742-0ec9-4f5c-a178-625220a46652.aspx. Just remember to turn it off when you're done debugging your service.



The step above is normally sufficient for most web service clients, but not for Silverlight. This is because exceptions are sent as SOAP Faults, and these are not supported in Silverlight 2, as explained in the next section.



SOAP Faults

Every HTTP response (including the SOAP message returned by the service) contains a status code (e.g. 200 - success, 404 - not found, or 500 - internal server error). The SOAP specification requires SOAP Faults to be sent with a status code in the 400/500 range, which indicates an error condition. Unfortunately, web browsers have a limitation with regards to status codes: When a browser plugin (such as Silverlight) makes an HTTP request, and the response status code is not 200, the browser hides the actual status code and the message body from the plugin. All Silverlight knows is that "something went wrong", but it has no way of discovering any details.



This browser limitation is the reason why SOAP Faults (and thus "exception propagation" from WCF) are not supported in Silverlight. Not all is lost though - you can often work around the issue as described in the next section.



Tricks, Workarounds and Future Plans

We've already seen that due to some fundamental restrictions on browser plugins, Silverlight cannot support SOAP Faults. To get around this, there are basically four approaches:



1. Do you really need the fault to get to the Silverlight client?

If all you need faults for is debugging, you can often debug in a different way. By using HTTP sniffing tools such as http://www.fiddlertool.com/fiddler/ or http://projects.nikhilk.net/webdevhelper/, you can observe faults directly on the wire, even if they don't get to Silverlight. You can also set up fault logging on the server side.



If all you need faults for is to be able to tell that "something went wrong", see the section on basic error handling at the end of this post.



2. Do you really need it to be a fault?

Suppose you have a web service operation like this:



[OperationContract] int GetCurrentTemperature();



The operation can actually return one of two things, an integer (in the case of success) or a SOAP Fault (in the case of failure). You can make it explicit in the contract:



[OperationContract] int GetTemperature(out MyFaultInfo fault);



Ugly, but it works. If you want to propagate exceptions, your entire GetCurrentTemperature implementation could be in a try/catch block, which returns null for fault if no exception happened, or the relevant information from the exception if one is caught.



In WCF, you can make this look somewhat nicer by using MessageContracts:



[MessageContract] public class MyStandardMessage {
[MessageBodyMember] public MyFaultInfo fault;


}



[MessageContract] public class TemperatureMessage : MyStandardMessage {

[MessageBodyMember] public int temperature;
}




[OperationContract] TemperatureMessage GetCurrentTemperature();



One caveat: If you're thinking of using actual exception types in your contract, like so:



[OperationContract] int GetTemperature(out System.Exception fault); //do not do this



... that is probably not a good idea. Exceptions may contain security-sensitive information that you would not want to reveal to a client. Also, in Silverlight exception types are generally not serializable, so you would not get the same .NET exception type in the Silverlight proxy. Finally, you may run into a number of issues around the Known Types mechanism (outside the scope of this post).



3. Does it have to follow the SOAP spec to the letter?

Strictly speaking, SOAP faults are not Silverlight-compatible. However, one can easily imagine a slight modification to the SOAP-over-HTTP protocol whereby faults will be sent with the 200 HTTP status code, thereby eliminating the problem. We are investigating along this direction for future versions of Silverlight, but there are things you can do even now.



If the service is in WCF, it should be possible to enable this "SOAP-for-the-browser" variant using WCF extensibility. In WCF, it is very easy to expose the same service in multiple variants (bindings) at different addresses, so you could easily expose the same service for the browser and for traditional SOAP clients by simply defining two endpoints in configuration.



If it is a third-party service, it should be possible to create a proxy (using WCF or another technology) that converts 400/500 status codes to 200. Some non-Microsoft web service stacks do not implement the SOAP specification correctly, and already return faults with a 200 code!



On the client side, you need to detect the fact that the message is a fault (e.g. by looking for the "soap:Fault" element) and handle it appropriately. This should be possible with WCF channel extensibility in Silverlight.



Notice that I am saying "should be possible". Actually making this work requires a deep knowledge of WCF. Our team will likely produce sample code that does this, effectively enabling faults when talking to a WCF service from Silverlight. Watch our Code Gallery site (http://code.msdn.com/SilverlightWS) where it will be posted when ready.



One more thing to note here: Since faults don't work by default, all of the fault-related code (such as the generic FaultExcetpion class) was cut from the Silverlight version of WCF in order to conserve size. Therefore, even with the approach above, the code you will have to write to handle faults will not be the same as what you may be used to writing in the regular .NET Framework. This may be addressed in a future version of Silverlight.



4. Does it have to work over the browser plugin model?

The browsers restrict information available to plugins (such as Silverlight) in case of a non-200 status code. However, there are alternatives to using the plugin model for making HTTP requests, some of which we are exploring for future versions of Silverlight.



One thing you can do along these lines even today is to use the JavaScript interop features of Silverlight, call out to JavaScript on the web page that hosts your Silverlight control, and then use the standard AJAX technique- xmlHttpRequest - to make your SOAP request and read the response. This lets you access the response regardless of the status code. However, you need to construct the requests and read the responses manually and so you lose the advantage of having a user-friendly automatically-generated proxy. Additionally, there are security restrictions on Silverlight code calling out to JavaScript, and this does not work for cross-domain access to services. Thus, this approach is probably not appropriate for most scenarios, but it is there as a last resort.



Basic Error Handling when Calling Services

Let's now consider the case where you don't actually care about getting any details about a web service fault. All you want to know is whether the call succeeded or not, and to handle the failure gracefully.



The normal way to invoke a service through a proxy is to call the ...Async method, and handle the result in the ...Completed event. For example, you set up the GetCurrentTemperatureCompleted event handler, and then call GetCurrentTemperatureAsync whenever you want to invoke the service.



However, when the call fails, you see exceptions thrown even before your GetCurrentTemperatureCompleted method starts executing, and there appears to be no way to catch them. What is going on?



Luckily, this was a temporary bug in Silverlight 2 Beta 2, and in the final version of Silverlight 2 this will all work correctly, as follows:



GetCurrentTemperatureCompleted always executes, regardless of whether the call succeeded or failed. If you try to access the Result property on the object that's passed to this method, and the call failed, an exception will be thrown (which you can catch). Also, you can check the Error property, which will be non-null only if the call failed.