If you've ever developed a Silverlight web application in conjunction with WCF services, you have probably run into some challenges when it comes to debugging the solution as a whole. Silverlight applications are limited to using services from the same server and port number as the application itself, unless the service explicitly allows external access through a clientaccesspolicy.xml file.
Now, in typical web service development I like to use the WCF Service Library project type, because it leaves your options for deployment open. WCF Service Host will be used when you run your solution from Visual Studio. Unfortunately, this means your application and your service will be running on different port numbers. The application will attempt to retrieve the clientaccesspolicy.xml file, but no such file exists. There is no straightforward way to host such a file, because the WCF Service Host is not a fully fledged web server.
Goals of this article
Looking around on the internet, you will find several blog posts on how to solve the problem described above. The focus of this blog post is turning the solution into something reusable. Ideally, it should be possible to use it with any WCF service, without modifying any of the service's code and without having to write a console hosting application like some blogs seem to suggest.
Hosting the client access policy file
The basic principle of hosting the policy file in WCF Service Host is to create a REST web service that outputs the file contents (implementation is available for download):
[ServiceContract]
public interface ICrossDomainPolicyService
{
[OperationContract]
[WebGet(UriTemplate = "clientaccesspolicy.xml")]
Stream GetClientAccessPolicyFile();
}
Reusability
Now that we have our utility service, how do we get it to run every time we host a WCF service that we want to access from Silverlight? It seems like a good idea to put the implementation in a separate assembly, that we can reuse in every WCF Library project. You would just add an extra service to your App.config, like this:
<service name="SampleService">
<endpoint address="http://localhost:8732/SampleService"
binding="basicHttpBinding"
contract="ISampleService" />
</service>
<service name="CrossDomainService.CrossDomainPolicyService">
<endpoint address="http://localhost:8732/"
binding="webHttpBinding"
contract=CrossDomainService.ICrossDomainPolicyService"
behaviorConfiguration="webHttpBehavior" />
</service>
<behaviors>
<endpointBehaviors>
<behavior name="webHttpBehavior">
<webHttp />
</behavior>
</endpointBehaviors>
</behaviors>
Unfortunately, this will not work. WCF Service Host will be unable to find the service contract or implementation we are referring to, because the assembly it is in will not be loaded automatically. Assembly qualified type names are not allowed for service implementation or contracts, so there is no way to get the assembly to load. Or is there?
WCF is very flexible; it allows you to write your own extensions. Those extensions can be implemented in external assemblies, so we're going to 'abuse' this mechanism to get our service to be loaded in the application domain. This can be done in the App.config file (Note that the type attribute value has the name of the assembly):
<system.serviceModel>
<services>
<!-- Services omitted for brevity -->
</services>
<extensions>
<behaviorExtensions>
<add name="crossDomainService"
type="CrossDomainService.CrossDomainServiceBehavior, CrossDomainService" />
</behaviorExtensions>
</extensions>
</system.serviceModel>
Hold on, because we are still not done. WCF will not load our assembly, unless the behavior extension is actually used. If we have to use it, we might as well make it do something useful, right?
Behavior extension implementation
Writing a behavior extension involves implementing the IServiceBehavior or IEndpointBehavior (IContractBehavior and IOperationBehavior make less sense for our purpose). To be able to use it from the configuration file, we will also have to implement the BehaviorExtensionElement abstract class.
We're going to write a behavior that will make sure that our CrossDomainPolicyService will get hosted on the appropriate address, without having to configure the service in the App.config file. We can do this by instantiating our own ServiceHost. The code shown below shows a simplified implementation using IEndpointBehavior. All the actual work is done in the AddBindingParameters method:
public class CrossDomainServiceBehavior : BehaviorExtensionElement, IEndpointBehavior
{
private static ServiceHost _serviceHost;
#region IEndpointBehavior Members
public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{
_serviceHost = new ServiceHost(typeof(CrossDomainPolicyService));
string address = new Uri(endpoint.Address.Uri, "/").ToString();
ServiceEndpoint crossDomainEndpoint = _serviceHost.AddServiceEndpoint(
typeof(ICrossDomainPolicyService),
new WebHttpBinding(),
address);
crossDomainEndpoint.Behaviors.Add(new WebHttpBehavior());
_serviceHost.Open();
}
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime) { }
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher) { }
public void Validate(ServiceEndpoint endpoint) { }
#endregion
#region BehaviorExtensionElement Implementation
public override Type BehaviorType
{
get { return typeof(CrossDomainServiceBehavior); }
}
protected override object CreateBehavior()
{
return new CrossDomainServiceBehavior();
}
#endregion
}
Now that we have our endpoint behavior, we can apply it in our configuration:
<system.serviceModel>
<services>
<service name="SampleService" behaviorConfiguration="serviceBehavior">
<endpoint address="http://localhost:8732/SampleService/"
binding="basicHttpBinding"
contract="ISampleService"
behaviorConfiguration="endpointBehavior" />
</services>
<behaviors>
<endpointBehaviors>
<behavior name="endpointBehavior">
<crossDomainService />
</behavior>
</endpointBehaviors>
</behaviors>
<extensions>
<behaviorExtensions>
<add name="crossDomainService"
type="CrossDomainService.CrossDomainServiceBehavior, CrossDomainService" />
</behaviorExtensions>
</extensions>
</system.serviceModel>
Our sample service is running on port 8732. The endpoint behavior we've written will ensure that http://localhost:8732/clientaccessspolicy.xml will spit out the necessary policy.
Downloadable version
We've come a long way, but there are still some issues that haven't been addressed. The complete implementation available for download differs in a number of ways:
- Proper handling for multiple endpoints/services in the same App.config
- An IServiceBehavior implemention was added. This adds the policy behavior on all the HTTP and HTTPS endpoints for the service.
- Adobe Flash compatible crossdomain.xml files are also served.
One last touch that makes the solution easier to use, is to sign the CrossDomainService assembly and install it in the global assembly cache. Although this is entirely optional, it saves you from the trouble of having to copy the assembly into the bin directories of each of your services.
The downloadable version includes a precompiled and signed assembly, as well as the source code and a sample application. As a bonus, it contains a second behavior extension that I might blog about soon. Please read the accompanying PDF file for additional instructions.
Files: