In preparation for my Silverlight for Business Applications webcast today, I decided to look more deeply into how Silverlight works with services. I originally concentrated a lot on security, and had a much larger post on how to secure your services, but ran into some issues there. More on that below.
WCF Service Requirements for Silverlight
In Silverlight 2 Beta 1, WCF services must be set up to use basicHttpBinding. Basic HTTP Binding sets up a SOAP 1.1 endpoint in your service. To enable that, before setting the service reference in your Silverlight application, change the service's web.config from the default wsHttpBinding to basicHttpBinding:
<system.serviceModel>
...
<services>
<service behaviorConfiguration="ServiceBehavior" name="Service">
<endpoint address=""
binding="basicHttpBinding"
contract="IService">
<identity>
<dns value="localhost"/>
</identity>
</endpoint>
<endpoint address="mex"
binding="mexHttpBinding" contract="IMetadataExchange"/>
</service>
</services>
</system.serviceModel>
If you forget to do that before adding a service reference, no big deal. Just refresh your service reference in your Silverlight project. If that doesn't work, remove and re-add the reference.
Enabling Exception Information in WCF
During debugging, you may want to get exception information back from WCF. In order to enable that, change the includeExceptionDetailsInFaults setting in the service's web.config to true:
<behaviors>
<serviceBehaviors>
<behavior name="TestServiceBehavior">
<serviceMetadata httpGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="true"/>
</behavior>
</serviceBehaviors>
</behaviors>
Adding the Silverlight Service Reference
Once your WCF service is running, you can add a reference from your Silverlight project. To do that, right click the Silverlight project and choose "Add Service Reference". Be sure to change the namespace to something useful, as this is the namespace that will be appended to the Silverlight application's namespace.
To avoid dealing with cross-domain configuration right at the moment, I added the service to the test web project. This ensures that the service and the Silverlight app are served from the same server and port.
Cross-Domain Support
If you want to enable your service to work cross-domain, there are two files you will want to concern yourself with:
crossdomain.xml
This file comes from back in the macromedia flash days. Since it has wide support on the internet, it makes sense for Silverlight to support it. You can read some documentation on this file format here.
One use supported by the specification is to specify domains which can access the service:
<?xml version="1.0"?>
<!DOCTYPE cross-domain-policy
SYSTEM "http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd">
<cross-domain-policy>
<allow-access-from domain="www.yoursite.com" />
<allow-access-from domain="yoursite.com" />
<allow-access-from domain="*.moock.org" />
</cross-domain-policy>
However, that detailed format is not supported by Silverlight. Instead, you must opt in to all domains if you want to use the crossdomain.xml approach. Such a policy file would look like this:
<?xml version="1.0"?>
<!DOCTYPE cross-domain-policy
SYSTEM "http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd">
<cross-domain-policy>
<allow-access-from domain="*" />
</cross-domain-policy>
However, since this format doesn't cover all the scenarios that Silverlight developers are likely interested in (and flash developers too, most likely), Microsoft has provided another cross-domain file that is more flexible and currently Silverlight-specific: clientaccesspolicy.xml.
clientaccesspolicy.xml
lf you want finer control over your cross-domain policy, you'll want to use clientaccesspolicy.xml. This file allows you to specify both what domains have access to your service, and also what subpaths are covered by the policy. You can include both clientaccesspolicy.xml and crossdomain.xml for your services; Silverlight will check this file before looking for the crossdomain.xml
The equivalent to the "allow all domains access to all services" is below:
<?xml version="1.0" encoding="utf-8"?>
<access-policy>
<cross-domain-access>
<policy>
<allow-from>
<domain uri="*"/>
</allow-from>
<grant-to>
<resource path="/" include-subpaths="true"/>
</grant-to>
</policy>
</cross-domain-access>
</access-policy>
If you want to get more selective about what you enable, you can specify external domains and subfolders on your site:
<?xml version="1.0" encoding="utf-8"?>
<access-policy>
<cross-domain-access>
<policy>
<allow-from>
<domain uri="http://contoso.com"/>
</allow-from>
<grant-to>
<resource path="/public-services/" include-subpaths="true"/>
</grant-to>
</policy>
</cross-domain-access>
</access-policy>
You can find additional information about cross-domain files on msdn.
Ian Griffiths discovered a small bug with the Silverlight-specific policy file in Beta 1, and documented it here.
Calling Your Service
The proxy and default configuration generated when you add the service reference will be adequate for your initial testing. The generated .ClientConfig file has specifics about your service. If you change your service, and refresh your reference, this file will be updated to reflect those changes. If you run into any problems with that, just delete the file, remove your service references, and re-add them.
ServiceReferences.ClientConfig File
<configuration>
<system.serviceModel>
<client>
<endpoint
address="http://localhost:47879/SilverlightApplication2_Web/Service.svc"
binding="basicHttpBinding"
bindingConfiguration="BasicHttpBinding_IService"
contract="SilverlightApplication2.ServiceReference1.IService"
name="BasicHttpBinding_IService" />
</client>
<bindings>
<basicHttpBinding>
<binding name="BasicHttpBinding_IService"
maxBufferSize="65536"
maxReceivedMessageSize="65536">
<security mode="None" />
</binding>
</basicHttpBinding>
</bindings>
</system.serviceModel>
</configuration>
Note that the default endpoint has the address in the file. You can override this in code if you desire. However, since this is a configuration file, you'll probably want to change (and maintain) the endpoint address in the config here, prior to deploying your app to test/production. Note, that for beta 1, this file is not used. That will change in Beta 2 or RTM.
The clientconfig file is deployed in the xap.
Calling Code
private void TestService()
{
ServiceReference1.TestServiceClient client =
new ServiceReference1.TestServiceClient();
client.GetTheValueCompleted +=
new EventHandler<TempBizAppDemo1.ServiceReference1.GetTheValueCompletedEventArgs>
(client_GetTheValueCompleted);
client.GetTheValueAsync();
}
void client_GetTheValueCompleted(
object sender,
TempBizAppDemo1.ServiceReference1.GetTheValueCompletedEventArgs e)
{
// act upon the information returned from the service
// e.Result;
}
As you may already know, all service calls in Silverlight 2 Beta 1 are async calls. Getting data from a service is a two-step operation. First, you set up an event handler which will be called when the service completes. Second, you call the service async method.
There's a lot of controversy about requiring async calls in Silverlight, so it may be something that changes in the future. I'll make no value judgements on it here. However, the current async implementation has the broadest support across browsers and operating systems. It also helps to ensure that the UI thread is not blocked (the calls go through the browser network stack, which is typically on the UI thread), and the browser UI remains responsive. Since async calls are a requirement in Beta 1, we'll just take that as a given.
The nice thing is, once you are used to the async pattern, the code is nice. The event handler you get has strongly-typed event args that include the return value from your service in e.Result (there is no e.Result if your service is a void function or VB sub)
Next Steps
At this point, if you have some UI hooked up to view the results from your service, you should be all set to go ... assuming message-level security doesn't matter (and it doesn't in many many cases as proven by the proliferation of AJAX applications out there calling open services without any message-level security)
Security
When it came to dealing with security like I normally would in a WCF service-based application, I ran into a couple brick walls and ran out of time. I did learn about some other options that may work, though.
The current MSDN documentation on securing your services for Silverlight 2 Beta 1 clients can be found here. It's pretty light, though.
I'm still feeling my way around some of the security options available to us, including tokens and rolling custom encryption. Look for a follow-up post once that is all sorted out. In the mean time, continue to secure your services like you would for AJAX applications (transport-level, HTTPS, IIS-based auth for the entire application) and have your authentication happen outside of Silverlight. Developers coming from the desktop world and those currently doing service calls from ASP.NET server-side will likely not be satisfied with that, so I promise to provide some good examples in the future :)
Update: Tim Heuer also has a great post on this topic. Take a look at it when you finish up here.