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.

Apr 22, 2008

"Pushing" Data to a Silverlight Application

The Web Service consumption feature in Silverlight 2 Beta1, based on the WCF technology and accessible through the "Add Service Refernce" dialog, makes it really easy for a Silverlight application to request data from a remote service, or to upload some data to a remote location.

But what if you want the service to initiate the conversation? Consider these examples:
  • An Instant Messaging / Chat application. When a user types a message into your Silverlight app, you use WCF to make, for example, a SendChatMessage() call. But what about receiving messages that others type back? Sure, you could call something like ReceiveChatMessage() continuously in a loop, and it could return null if there are no messages waiting. But this is a lot of work, not to mention the severe implications this would have for the performance of your app. Wouldn't it be nice if it "just worked"?
  • A Monitoring application. A lot of people are writing applications that monitor something that can be changed at any time on the server. Maybe it's a stock ticker / charting app, or maybe it's monitoring some physical sensor... Again, you could continuously poll the server to see if anything changed, but this is inefficient.

To solve problems like this, we are trying to ship a "duplex" feature in Silverlight 2 Beta2. (There is a very good chance that the feature will make it in, but of course no guarantees, and anything I say about the design is subject to change by the time we ship). I'll first give you a quick overview and then the details:

Quick Overview

Let's say you want to build a chat application. You'll start by defining a chat message such as the following:


[DataContract]
public class ChatMessage {
[DataMember] public string messageText;
[DataMember] public int textColor;
[DataMember] public bool isSmileyFace;
}

You would then create a "duplex service" on the server-side the way you would normally create a duplex service in WCF (more info below). The last step would be to configure the WCF service with a special "Silverlight duplex binding" by making an appropriate modification to Web.config.

In Silverlight, you will not be able to do an "Add Service Reference" to this service. Instead, you will manually copy the ChatMessage class to your Silverlight project. Then, after adding a new Extension assembly to your Silverlight project as well as some sample code we'll provide, you'll be able to do something like this:


var r = new DuplexReceiver<ChatMessage>("http://example.com/myService.svc");
r.OnMessageReceived += new OnMessageReceivedEventHandler<ChatMessage>(r_OnMessageReceived);


And that's all! Whenever the server chooses to send a message, you will need to handle this event by providing the following callback method:



static void r_OnMessageReceived(object sender, OnMessageReceivedEventArgs<ChatMessage> e)
{
//Handle a chat message received from the server
//E.g.:
chatBox.Text = chatBox.Text + e.Result.messageText;
//...
}

Details

For those of you familiar with WCF: What we're actually aiming to ship in Beta2 are two things:
- A new binding for WCF on the server side
- The client-side equivalent of this binding for WCF-in-Silverlight
What is not included in the plan (for Silverlight 2, not just for Beta2), is any Service Model support (like callback contracts and the like), meaning no "Add Service Reference" support.

Normally, this would mean that you need to be a WCF guru to use this (create a channel manually, receive a WCF Message, deserialize). Fortunately, we are hoping to ship sample code for DuplexReceiver < T > , that will make simple scenarios really easy (like you saw above).

For an overview of building the server side of duplex services with WCF, a good starting point is http://msdn2.microsoft.com/en-us/library/ms731064.aspx and http://msdn2.microsoft.com/en-us/library/ms731184.aspx- but here too, we may provide some sample server-side code to make this easier.

"Under the hood", the duplex mechanism works by polling the server using HTTP requests. However, this is "smart polling": the polling request to the server will not immediately return if there are no messages, but will linger around and return only when a message actually becomes available. There is a lot more to this but I can't talk about it yet (except for referring to Clarke's Third Law :)

This post is mostly to get the basic information out there - there are a lot more details to cover, which I may do in future posts.




Apr 15, 2008

Debugging Web Service Usage in Silverlight 2

Silverlight 2 Beta1 makes it easy to use Web Services based on either the WCF technology (Windows Communication Foundation), the “.asmx” technology (ASP.NET Web Services), or practically any other SOAP platform. Unfortunately, when something goes wrong with service consumption, you often run into cryptic and incomprehensible error messages that don’t help you much. We are looking into various ways to make this better by the time we fully ship Silverlight 2, but for now I hope that this post will be useful in helping you debug common problems. Here are the things you can try:

Does your proxy compile?
After you use the “Add Service Reference” dialog to add a reference to a service, try building your project. If you get compilation issues in the generated proxy code, you are probably using a service that uses some feature that is not supported in Beta1. We are trying to fix all or most of these by the time we ship, but for now the easiest thing to do is to find the offending code in the generated proxy and just remove it – naturally this workaround does not work in all cases :) Some common things that will cause non-compilable proxies in Beta1:


- Using custom SOAP headers in your service
- Using custom SOAP faults
- Using “wildcard” schema in your service like xsd:any or xsd:anyattribute

And specifically if your service is a WCF (or in some cases .ASMX) service:


- Using XML types like XmlElement/XElement/XmlNode[]/XmlAttribute/etc. in your service
- Using Datasets in your service
- Using types that implement ISerializable in your service (except for collections)
- Using WCF Transactions features
- Using the Stream type in your service
- Using MessageHeaderAttribute in Message Contracts

Check the Configuration
The next step is to check whether the service is configured correctly. You should have a file called ServiceReferences.ClientConfig generated by Add Service Reference. It actually doesn’t do anything at all in Beta1! This (understandably) confuses a lot of people. Starting with Beta2, we will actually start using this file, but for now any changes that you’ll make in it won’t actually affect anything.

However, the file is still useful for debugging. Look in the file – it should look something like this:


<configuration><system.serviceModel>
<client>
<endpoint address="http://localhost:5678/TestService.svc" binding="basicHttpBinding"
contract="MyProject.ITestService" name="default" />

</client>
</system.serviceModel>
</configuration>

Notice that it has exactly one endpoint element for the service you just added a reference to, and the binding is basicHttpBinding.

If you don’t see an endpoint element for your service, chances are your service wasn’t actually configured correctly for Silverlight consumption. Silverlight can only talk to simple SOAP services at this point – only SOAP 1.1, without any advanced WS-* protocols like WS-Addressing or WS-Security. If you are using “.ASMX” web services, they should just default to this simple configuration. If you are using WCF services, you need to check the configuration on the service side. Open web.config in your service-side project and find a place that looks like the following (usually towards the end):

<system.serviceModel>
...
<services>
<service name="MyProject.TestService">
<endpoint address="" binding="basicHttpBinding" contract="MyProject.ITestService" />
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
...
</service>
</services>
</system.serviceModel>


Find the endpoint whose “contract” attribute refers to your service. Make sure that the “binding” attribute is set to “basicHttpBinding”. Unfortunately the default for WCF is “wsHttpBinding”, but it doesn’t work with Silverlight. We are hoping to ship a Visual Studio item template in the future (“Add New Item… Silverlight-enabled WCF Service”) that will have a number of Silverlight-friendly defaults, including the correct binding.


It is ok to have other endpoint elements for other contracts with other bindings - for example, do not change the "mexHttpBinding" in the second endpoint element above.


You should check your service-side configuration even if the client-side ServiceReferences.ClientConfig appears to be correct.

If ServiceReferences.ClientConfig contains more than one <> for your service, you may need to use a more complicated constructor to new up your service proxy – the one that takes a Binding and an EndpointAddress. Not to worry – just pass a new BasicHttpBinding() and a new EndpointAddress built from the URL of your service.



By the way, our long-term (post-Beta1) plans for the config file are described here: http://blogs.msdn.com/suwatch/archive/2008/04/07/tutorial-using-silverlight-web-service-client-configuration.aspx

Check if the Service is Running
Before looking for problems on the Silverlight side, it is useful to first check whether the service itself is working. A quick-and-dirty way to check is to just type the service address into a web browser (not the address you typed into the “Add Service Reference” dialog, but the address you can find in ServiceReferences.ClientConfig). In many cases, the “service help page” feature will be turned on and you will see either a page indicating that the service is running, or an error page which you can use as a starting point for debugging.

A more reliable way to test whether the service is working is to use a test tool such as the WCF Test Client (http://msdn2.microsoft.com/en-us/library/bb552364.aspx) to try and talk to the service.

Finally, an almost sure-fire but sometimes lengthy way of finding out whether the problem is with the Silverlight code or with the service code is to try using the service without Silverlight :) Just create a new project of type “Console Application”, do an Add Service Reference to that project just like in Silverlight, and write service consumption code inside Main() – again, just like in Silverlight. Use Console.WriteLine to show the results.

Check for Cross-Domain Issues
Start your Silverlight application. Note the URL that appears in the browser (e.g. http://localhost:1111/something/MyGraphicalApp.aspx) – this is your “Application URL”. (Actually, what matters here is the URL of the XAP file, e.g. http://localhost:1111/somethingelse/MyGraphicalApp.xap, but in most simple cases this URL would be in the same domain as the hosting web page, so I’m ignoring the difference for now).

Then, look at the URL in the ServiceReferences.ClientConfig file – e.g. http://localhost:5678/foo/TestService.svc or local.live.com/SearchService/LocalSearch.asmx - this is your “Service URL”.

In Beta1, both the “Application URL” and the “Service URL” must be HTTP URLs (not HTTPS) for service consumption to work. This is the first thing to check.

Also, a common mistake is to run your Silverlight application from a file:// Application URL, resulting in cross-domain issues. Sometimes, you run into this if you just hit F5 to run your Silverlight app – instead, right-click on the .aspx page in your project and choose “View in Browser”.

Now you need to figure out the domains for these URLs. The domain is just the basically everything between http:// and the first slash / after that, including the port number (assumed to be 80 if not present). If the domain of the Application URL is different in any way from the domain of the Service URL in any way (even if it’s just a port number difference, or just one part of the domain name is different), you have a cross-domain situation. For example, if your app is at http://localhost:1111/something/MyGraphicalApp.aspx and it is trying to call into http://localhost:5678/foo/TestService.svc, you have a cross-domain situation because localhost:1111 is different from localhost:5678.

Silverlight documentation tells you that if you are in a cross-domain situation, you need to have a “cross-domain policy file” (clientaccesspolicy.xml or crossdomain.xml) present if you want services to work. There is an easy way to check if you have everything set up correctly: Just open a browser and browse to http://service_domain/clientaccesspolicy.xml and http://service_domain/crossdomain.xml. If at least one of these is present, valid and allows cross-domain access – you’re fine. If not, you need to make sure at least one of these files is present.

A common mistake is to put the cross-domain policy file not directly at the root of the domain – for example, at http://localhost:5678/foo/clientaccesspolicy.xml instead of at http://localhost:5678/clientaccesspolicy.xml. It is easy to run into this situation when working with older (.NET 2.0) projects – see http://timheuer.com/blog/archive/2008/04/09/silverlight-cannot-access-web-service.aspx.

Also, make sure to check the syntax of these files – an error in parsing will be treated essentially the same way as if the file was not there. Also note that clientaccesspolicy.xml and crossdomain.xml have different syntax – make sure you use the appropriate content for the file you choose.

A neat trick for adding cross-domain policy files to WCF services that are not hosted in IIS is described here: http://blogs.msdn.com/carlosfigueira/archive/2008/03/07/enabling-cross-domain-calls-for-silverlight-apps-on-self-hosted-web-services.aspx

Enable Exceptions
Normally, Silverlight does not give you much information in exception messages. This decision was made to make the Silverlight download size smaller (exceptions take up a lot of space, especially once they’re translated into all of the languages Silverlight supports). You can read more about this, and find out how to turn on full exceptions for debugging, here: http://blogs.msdn.com/silverlightws/archive/2008/04/06/getting-full-exceptions-in-silverlight-2-beta-1.aspx

Also, if you are using a WCF service, WCF does not normally return much information about server-side exceptions. This is a security measure: you normally don’t want to expose internal information about your service to the outside world. However, during debugging, you can change this by following the steps here: http://blogs.devdeo.com/carlos.medina/PermaLink,guid,b3bff742-0ec9-4f5c-a178-625220a46652.aspx. Make sure to turn it off once you’re done debugging!

Finally, this seems obvious but a lot of people forget about it for some reason - when you get an exception, don’t just look at the exception itself - look at its innerException, and the innerException inside it, etc. Especially in web service scenarios, the truly useful exceptions are often “wrapped” in several layers.

Unfortunately, even with all of the steps above, you often still won’t see much useful information when a web service call fails. This is due to a pretty fundamental limitation of Silverlight Beta1 (that unfortunately may stay around even after the Beta) – the lack of support for SOAP Faults. So, to truly debug, you need to see what is actually going on – this is what the next section is about.

Getting Down to the Wire
The ultimate way to debug Silverlight service consumption issues is by using an “HTTP spying” tool such as http://www.fiddlertool.com/fiddler/ or http://projects.nikhilk.net/webdevhelper/ that shows you the actual HTTP traffic as it happens. Before using it, make sure to enable server-side exceptions (see above).

Start your Silverlight app and your tool side-by-side, and make your app do a web service call. Here are some common patterns you will see:

1.Nothing
- Your HTTP spying tool may be misconfigured (try running another, non-Silverlight app, like a regular web browser, to make sure it works)
- Your app may be broken – put a breakpoint where you invoke the service, make sure it’s hit
- You may have a configuration issue – see Configuration section above
- You may be trying to host your application from a file:// or https:// URL, or trying to call an https:// service

2. Just a service request
- Congratulations – you’ve avoided a cross-domain situation if you see the actual request to the service on the wire.
- Is the address what you expected it to be?
- Look at what’s coming back. Is it timing out or are you getting a 404? Your service or even WCF itself may not be set up correctly.
- Are you getting a SOAP fault back? If so, read carefully what’s in the fault – it will usually have enough information to debug the issue.
- In general, look very carefully at both the request and the reply (including the body and HTTP headers)
- Did you remove anything to get the proxy to compile? The service may actually require some items that you removed (e.g. may require certain SOAP headers)

3. A request to clientaccesspolicy.xml (and possibly crossdomain.xml afterwards), followed by a request to the service
- Congratulations – you have a cross-domain situation but you have successfully set up your cross-domain policy files
- This is now equivalent to situation #2 above

4. Just a request to clientaccesspolicy.xml and nothing else
- Most likely a malformed clientaccesspolicy.xml, or one that contains a policy that forbids cross-domain access

5. A request to clientaccesspolicy.xml, then crossdomain.xml, then nothing else
- Basically, something went wrong with cross-domain policy files.
- Are you getting 404s back? Check if you’re hosting the files correctly and at the correct locations
- Are you getting the actual files back? They may be malformed or may forbid cross-domain access